6

I have a data class, something like this:

public class Person {
   private String name;
   private Long code;

   // corresponding getters and setters
}

I want to write two web services that provide two different JSON representation of Person. For example, one of them provide {"name":"foo"} but the other one {"name":"foo", "code":"123"}.

As a more complicated scenario, suppose that Person has a reference property, for example address. Address has its own properties as well and I expect that both of my web services consider this property but each of which do this in their own manner.

How should my SpringMVC views be like?

Please note that I'm new to SpringMVC. So give me a sample code beside your answer, please.

UPDATE 1: After few days, all answers push me to solve the problem in controllers or by annotating the data classes. But I want to do this in views, without any more java codes. Can I do it in JSP files or thymeleaf templates or even in .properties files?

UPDATE 2: I found json-taglib. But somehow it is left out of new upgrades. Is there any similar solution?

vahidreza
  • 843
  • 1
  • 8
  • 19
  • 2
    Use a single Business Object Model with two Data Transfer Object Models. – Compass May 24 '18 at 14:45
  • I want to solve the issue in soft and configurable way, something like JSP template or .properties files. In addition, assume that I want to have a lot of different representations. Really should I write dozens of class for this simple issue? – vahidreza May 24 '18 at 14:52
  • can you provide use cases ?? – rohit thomas May 28 '18 at 03:03
  • What do you mean? I've given example in the question. Isn't it enough? – vahidreza May 28 '18 at 10:48
  • I don't understand. You said in question you want to write two controllers but now you are saying you want to write it in jsp. You didn't mention when do you want to provide different outputs relating to views means on same submit or different submit buttons or differnt jsp. Any ways,from what I understood, i can tell you is return the same person object from the controller which will be converted by spring into json and then use it on frontend as required. Edit your question to make it completely clear. – Shubham Kadlag May 30 '18 at 13:42
  • @ShubhamKadlag Thank you. You're right. I did it. I think it has got more understandable now. I'm waiting for your idea after this modification. – vahidreza May 30 '18 at 20:40

6 Answers6

5

You're using Spring-MVC so Jackson is in charge of JSON serialize and deserializing.

In this case, you can use @JsonInclude(Include.NON_NULL) to ignore null field during serialization.

public class Person {
   @JsonInclude(Include.NON_NULL)
   private String name;

   @JsonInclude(Include.NON_NULL)
   private Long code;

   // corresponding getters and setters
}

If your name or code is null then it is excluded from output JSON

So if you pass code as null, your ouput JSON will look like {"name":"foo"}

Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
  • I modified the question. Please read the UPDATE. I don't want to do anything java codes. I think Spring MVC provide this feature in views. – vahidreza May 24 '18 at 19:53
5

When creating JSon with Spring MVC the "view renderer", by default, is Jackson. There is no need to use things like JSP or other view technology. What you want to do, is to tell Jackson how to render an object for a given situation. Multiple options are available, but I would suggest to use projections. An example:

@RestController
@RequestMapping(value = "person")
public class PersonController {

    private final ProjectionFactory projectionFactory;

    public PersonController(ProjectionFactory projectionFactory) {
        this.projectionFactory = projectionFactory;
    }

    @GetMapping("...")
    public PersonBase getPerson(..., @RequestParam(value = "view", required = false, defaultValue = "base") String view) {
        ...

        if(view.equals("extended")) {
            return projectionFactory.createProjection(PersonExtended.class, person);
        } else {
            return projectionFactory.createProjection(PersonBase.class, person);
        }
    }
}



public interface PersonBase {
    String getName();
}

public interface PersonExtended extends PersonBase {
    Long getCode;
}

The view layer of your application are the projection classes (put then in one package, the view package if you wish). A Controller can choose what view to render, or you could make the result dynamic as in the example.

Your question on how to render the address could be solved with another projection like this:

public interface PersonWithAddress extends PersonExtended {
    AddressProjection getAddress();
}

public interface AddressProjection {
    String getStreetName();
    String getZipcode();
    ...
}
JohanB
  • 2,068
  • 1
  • 15
  • 15
4

You can look for dynamic filtering of fields using MappingJacksonValue.

The filter allows you to serialize fields that meet custom criteria.

You can check my sample code here:

package com.github.tddiaz.jsonresponsefiltering;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import jdk.nashorn.internal.objects.annotations.Getter;
import lombok.Data;
import lombok.NonNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RestController
    class Controller {

        @GetMapping("/filter")
        public ResponseEntity filter() {
            final Response response = new Response("value1", "value2", "value3");

            //ignore field3; will only return values of field1 and field2
            final SimpleBeanPropertyFilter beanPropertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");

            final FilterProvider filterProvider = new SimpleFilterProvider().addFilter("responseFilter", beanPropertyFilter);

            final MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(response);
            mappingJacksonValue.setFilters(filterProvider);

            return ResponseEntity.ok(mappingJacksonValue);
        }
    }

    @JsonFilter("responseFilter")
    @Data
    class Response {
        @NonNull
        private String field1, field2, field3;
    }
}
Grant Miller
  • 27,532
  • 16
  • 147
  • 165
Mafuj Shikder
  • 141
  • 1
  • 8
  • Sorry, I didn't get you. I can't identify what your answer has more than @Mạnh 's. Honestly, it seems that your answer need more java codes, whereas I prefer not to write java code. Do you mind explaining more? – vahidreza May 27 '18 at 19:39
2

Use Projection for expose in diferent manners your json, and ResourceProcessor if you need to add more info to this model through projection, e.g another DB table perhaps.

Mafuj Shikder
  • 141
  • 1
  • 8
  • Thank you for your attention, but I think didn't get my wish. Of course it's my fault because I can't explain it that good. Briefly, I want to solve the issue in presentation layer, more precisely in views (I mean jsp, thymeleaf and etc). If I understood correctly, you guys insist on solving the problem in business model or via annotating data classes. Really there is no way to make it in views? – vahidreza May 27 '18 at 23:25
2

Based on your use case, just call the controller of your choice from the jsp/js page ...For e.g. Let's say Admin is the user then call AdminController else call User Controller ...this can be done using a simple if/else condition...you can also look into Proxy Design Pattern but that depends on the use case

rohit thomas
  • 2,302
  • 11
  • 23
  • Did you answer my question? ;) – vahidreza May 28 '18 at 11:55
  • @vahidreza So based on your model class you can assign the values to Setter methods or not with respect to the Controller e.g. In Admin controller you will set for "{"name":"foo", "code":"123"}." and User controller you can set only for {"name":"foo"} which will use the same model class.I hope I did :P – rohit thomas May 29 '18 at 02:37
  • I was kidding you. ;) You're right. Of course you are still saying that I have to write java codes and so that's not what I want. – vahidreza May 29 '18 at 14:37
  • The reason everyone suggests to do it in the Java side is because you have control over the data being sent....Even if you do it at the presentation layer (JSP) you will still be exposing the JSON object to the user, which can be easily viewed using a debugger on your browser, for which you will have to encode/decode your JSON case in point, it safer to do it on the Java side.But I do hope you find a solution to do it on the JSP side. Cheers :) – rohit thomas May 30 '18 at 02:42
2

I recommend you to use JSON.stringify(value[, replacer[, space]]) function on frontend. I have given an example below. You have a write a custom function according to your requirements on the specific view.

Eg. The below example ignores null values. Here I have written editperson function which removes null values.

The function has two input parameters i.e. key and value. Write your logic according to the keys and values which you want to remove or change.

var springperson = { "name":"foo","code":null }

console.log(springperson); // person recieved from  spring


function editjson(key, value){
if (value !== null) return value
}

var editperson = JSON.stringify(springperson, editjson); // String representation of person

var personjson=JSON.parse(editperson);  // JSON object representation of person

console.log(personjson); // person as required by the view

Comment if you have any issues.

Shubham Kadlag
  • 2,248
  • 1
  • 13
  • 32
  • Excuse me, but your answer doesn't make sense. I could understand the relation between my desire and what you suggest. – vahidreza Jun 01 '18 at 08:42
  • As you see, no one is able to understand what you are trying to suggest. You can add your code for more clarity. I still don't understand what you are trying to suggest clearly even though I am very much familiar with Spring MVC. – Shubham Kadlag Jun 01 '18 at 09:03
  • I appreciate you for spending your time and making your effort. But I have to say some words. Maybe at the beginning, It was unclear. But at the moment, I think that it has got clear, edition by edition, thank commenters. Also other answers are correct whereas they are a little different from my wish. However your answer isn't. I'm sorry to tell it in this manner but it's reality. Your answer really doesn't make sense. Should I solve problem in frontend?! Is JSON.stringify related to my questoin?! I think you didn't get the question. Please read it carefully, please! – vahidreza Jun 01 '18 at 09:48
  • So lets get this clear. Tell me if you have two different requestmapping for your two webservice. – Shubham Kadlag Jun 01 '18 at 09:52
  • RequestMapping is no big deal. I want to know how to serialize my java classes to json so that each class get serialized depend on the webservice. Just same as the situation that you want to have two different view for a list of photos in a photo album app, for example. In this example, you write two jsp views. One of them present only name and thumbnail of each photo, but the other one show name,dates,creator and this kind of informations. – vahidreza Jun 01 '18 at 10:05
  • Okay, but for that you send name, thumbnail ,dates,creator and all kind of information and then filter it the respective view. Similarly, I am suggesting you to send the complete Person object on the views and then filter them. What others have answered is you filter that it in java class but you don't want to make changes in java class so I am telling send the same complete Person object and then apply filters on the views. – Shubham Kadlag Jun 01 '18 at 10:15
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172230/discussion-between-shubham-kadlag-and-vahidreza). – Shubham Kadlag Jun 01 '18 at 10:16