问题
I was trying to implement a custom serializer for one of the properties of my object to get a different JSON structure when I return it from my REST controller.
My constraints are I cannot change the interface of the REST controller or the model classes (so I cannot add extra annotation etc, that would maybe make this easier). The only thing I could think of, making it render different than described in the model is a custom serializer, if there are any better approaches for this, please don't hesitate to tell me a different approach that is within the constraints.
My models look something like this:
public class WrapperModel {
// a lot of autogenerated fields
List<Property> properties;
// getters/setters
}
public class Property {
private String name;
private String value;
// getters / setters
}
So when this is rendered is looks like so:
{ ....
"properties": [
{"key1": "value1"}, {"key2": "value2"},...
]
}
What I would want is this:
{ ....
"properties": {
"key1": "value1",
"key2": "value2",
...
}
}
The serializer for this is easy enough:
public class PropertyListJSONSerializer extends StdSerializer<List<Property>> {
//....
@Override
public void serialize(List<Property> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
for(Property p: value){
gen.writeStringField(p.getName(), p.getValue());
}
gen.writeEndObject();
}
}
Now when I try to register this serializer inside a @Configuration
file:
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
SimpleModule module = new SimpleModule();
module.addSerializer(List<Property>.class, new PropertyListJSONSerializer());
mapper.registerModule(module);
return mapper;
}
this doesn't work, because List<Property>.class
cannot be used for addSerializer
since it's a template class. Is there any other way to add this serializer or something that does something similar?
I do not want to add a custom serializer for WrapperModel
since this class is autogenerated and fields can be added and removed. This should be possible without modifying the application code (if I had a custom serializer you would need to add/remove the fields from the serializer also(?)). Or is there a way to just use the Standard serializer for the class and just manually handle this one List<>
field.
The model classes are generated by the Spring Boot openapi code generator, so there is a very limited set of JSON annotations I can put on top of the model fields (if there's an annotation way, please dont hesitate to post as I can check in the openapi sourcecode if that particular annotation is supported). But I would rather go with either a custom serializer for List<Property>
if that is at all possible or writing a serializer for WrapperModel
that uses StdSerializer
for everything and only handle the List
property myself.
回答1:
MixIn
In that case we need to use MixIn
feature. Create interface like below:
interface WrapperModelMixIn {
@JsonSerialize(using = PropertyListJSONSerializer.class)
List<Property> getProperties();
}
and register it like below:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(WrapperModel.class, WrapperModelMixIn.class);
Older proposal
You need to use Jackson
types which allow to register serialiser for generic type. Your serialiser after change could look like below:
class PropertyListJSONSerializer extends StdSerializer<List<Property>> {
public PropertyListJSONSerializer(JavaType type) {
super(type);
}
@Override
public void serialize(List<Property> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
for (Property p : value) {
gen.writeStringField(p.getName(), p.getValue());
}
gen.writeEndObject();
}
}
And you can register it as below:
ObjectMapper mapper = new ObjectMapper();
CollectionType propertiesListType = mapper.getTypeFactory().constructCollectionType(List.class, Property.class);
SimpleModule module = new SimpleModule();
module.addSerializer(new PropertyListJSONSerializer(propertiesListType));
mapper.registerModule(module);
来源:https://stackoverflow.com/questions/55240648/spring-boot-custom-serializer-for-collection-class