14

Problem

I have a Spring MVC application that requires me to translate the id's and names of a list of a certain entity to an array of JSON objects with specific formatting, and output that on a certain request. That is, I need an array of JSON objects like this:

{
     label: Subject.getId()
     value: Subject.getName()
}

For easy use with the jQuery Autocomplete plugin.

So in my controller, I wrote the following:

@RequestMapping(value = "/autocomplete.json", method = RequestMethod.GET)
@JsonSerialize(contentUsing=SubjectAutocompleteSerializer.class)
public @ResponseBody List<Subject> autocompleteJson() {
    return Subject.findAllSubjects();
}

// Internal class
public class SubjectAutocompleteSerializer extends JsonSerializer<Subject> {

    @Override
    public void serialize(Subject value, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeStringField("label", value.getId().toString());
        jgen.writeStringField("value", value.getName());
        jgen.writeEndObject();
    }
    
}

The JSON I get back however, is the default serialization inferred by Jackson. My custom serializer seems to be completely ignored. Obviously the problem is incorrect usage of @JsonSerialize or JsonSerializer, but I could not find proper usage of these within context anywhere.

Question

What is the proper way to use Jackson to achieve the serialization I want? Please note that it's important that the entities are only serialized this way in this context, and open to other serialization elsewhere

Community
  • 1
  • 1
DCKing
  • 4,253
  • 2
  • 28
  • 43
  • I realize this thread is quite old, but I'm running into this same problem. Since there is no accepted answer, I'm wondering if you found a good answer. As you've indicated, it's undesirable to annotate the entity directly as suggested below. IMHO, the approach you tried is the simplest and most elegant/semantic solution, but of course it isn't supported. It seems you have to jump through hoops with jackson/spring to do this - the only solution I've found requires creating and configuring custom ObjectMappers and involves quite a bit of boilerplate code/config. Is there any better approach? – shelley Mar 20 '12 at 17:54
  • Unfortunately I haven't fixed this. Although requesting JSON client side was our first preference when implementing it, this specific list of items turned out small enough to be put on the page at once. This is what we turned out doing, so we have a pure client side solution and no JSON to load. If you ever find out the best way to do this, I'd much appreciate knowing about it, however! – DCKing Mar 26 '12 at 20:43
  • I don't know if its the exact same issue, but I was running into something similar where my JsonSerializer class was being ignored. I had to specifically add the serializer as a module to my objectmapper instance. In kotlin: `val mod = SimpleModule(); mod.addSerializer(Timestamp::class.java, MyTSConverter()); objectMapper.registerModule(mod)` – orpheus Aug 20 '21 at 15:56

2 Answers2

9

@JsonSerialize should be set on the class that's being serialized not the controller.

roottraveller
  • 7,942
  • 7
  • 60
  • 65
Henrik Barratt Due
  • 5,614
  • 1
  • 21
  • 22
  • 1
    I figured out as much. Do you have any directions on how to implement a custom JSON serializer in my Controller? – DCKing Aug 09 '11 at 13:38
  • 2
    I would skip the serializer and just annotate the properties in the Subject class with @JsonProperty("label") for id and @JsonProperty("value") for name – Henrik Barratt Due Aug 09 '11 at 21:11
  • I annotate at the class that's being serialized but the serialize method never got invoked. I have to create ObjectMapper and register it into Spring MVC as in http://www.codeblog.it/en/snippet/java/2013/05/23/register-custom-json-serializer-jackson-spring-mvc-3 – Lee Chee Kiam Aug 16 '13 at 15:30
  • [this baeldung article](https://www.baeldung.com/jackson-annotations#7-jsonserialize) has the `@JsonSerialize` annotation on the specific field to be serialized. When I try this though, it serializes all fields of the same type which is not what I expected – orpheus Aug 20 '21 at 15:52
0

@JsonSerialize should be set on the class that's being serialized not the controller.

I'd like to add my two cents (a use case example) to the above answer... You can't always specify a json serializer for a particular type especially if this is a generic type (erasure doesn't allow to pick the the serializer for a particular generic at runtime), however you can always create a new type (you can extend the generalized type or create a wrapper if the serialized type is final and can't be extended) and custom JsonSerializer for that type. For example you can do something like this to serialize different org.springframework.data.domain.Page types:

@JsonComponent
public class PageOfMyDtosSerializer
        extends JsonSerializer<Page<MyDto>> {
    @Override
    public void serialize(Page<MyDto> page,
                          JsonGenerator jsonGenerator,
                          SerializerProvider serializerProvider)
            throws IOException {
    //...serialization logic for Page<MyDto> type
    }
}

@JsonSerialize(using = PageOfMyDtosSerializer.class)
public class PageOfMyDtos extends PageImpl<MyDto> {
    public PageOfMyDtos(List<MyDto> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }
}

And then you can return your type from methods of your services - the necessary serializer will be utilized unambiguously:

@Service
@Transactional
public class MyServiceImpl implements MyService {

    ...

    @Override
    public Page<UserProfileDto> searchForUsers(
        Pageable pageable,
        SearchCriteriaDto criteriaDto) {

        //...some business logic

        /*here you pass the necessary search Specification or something else...*/
        final Page<Entity> entities = myEntityRepository.findAll(...);

        /*here you goes the conversion logic of your choice...*/
        final List<MyDto> content = modelMapper.map(entieis.getContent(), new TypeToken<List<MyDto>>(){}.getType());

        /*and finally return your the your new type so it will be serialized with the jsonSerializer we have specified*/
        return new PageOfMyDtos(content, pageable, entities.getTotalElements());
    }
}
Pavel B
  • 1
  • 1