Recently I have migrated the project from Jersey to Spring Rest. Previously boolean field was serialized as String like success: "true", now it became without quotes success: true. It wouldn't be a problem but old apps rely on it and can't deserialize. How to return boolean values as String in Spring Boot? Any spring.jackson.serialization property?
If you don't have access to this field, create a class which will do serialization for it:
public class StringBooleanSerializer extends JsonSerializer<Boolean> {
#Override
public void serialize(Boolean bool, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(bool ? "true" : "false");
}
}
Register it with your object mapper:
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(new StringBooleanSerializer());
objectMapper.registerModule(simpleModule);
That's it. However if you have access to this field you can do just this:
#JsonSerialize(using=StringBooleanSerializer.class)
private Boolean bool;
The same goes with deserialization if necessary.
Write your custom serializer and deserializer and use following annotations on that boolean field:
#JsonSerialize and #JsonDeserialize.
Related
This question already has an answer here:
Is there some way to specify a custom deserializer for a type that is nested inside an Optional for Jackson?
(1 answer)
Closed 3 years ago.
Say I have a simple String custom serializer and deserializer.
public class SimpleSerializer extends StdSerializer<String> {
protected SimpleSerializer() {
super(String.class);
}
#Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null) {
return;
}
value = doThings(value);
gen.writeString(value);
}
}
I use this on various String fields but now I need to use it on an Optional<String> field too.
#JsonSerialize(using = SimpleSerializer.class)
#JsonDeserialize(using = SimpleDeserializer.class)
private Optional<String> item;
This doesn't work unfortunately since the class does not match. And I can't assign it like Optional<#JsonSerialize... String> item because the annotation is not marked with TYPE_USE target. I tried creating custom annotation with TYPE_USE, #JacksonAnnotationsInside and the others as a workaround but that didn't work either, which is not a surprise.
Is there any way that I can use the serializer and the deserializer without creating another one for specifically Optional<String>?
First, include the Jackson Module for Optional. It's part of
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
</dependency>
Second, register it with your ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
Finally, annotate your field with with #JsonSerialize and #JsonDeserialize but use their contentUsing element to specify your custom serializer/deserializer
#JsonSerialize(contentUsing = SimpleSerializer.class)
#JsonDeserialize(contentUsing = SimpleDeserializer.class)
The JsonSerializer used to serialize Optional fields, OptionalSerializer, will know to delegate to SimpleSerializer to perform the actual serialization. The same behavior applies to deserialization.
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.
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);
I have a problem with deserialization json into POJO class which looks like this:
#Data
public class Foo {
private String fieldA;
private String fieldB;
private IBar fieldC;
}
IBar is an interface which defines getters for some classes. One of the solutions what I found is to use #JsonDeserialize(as = BarImpl.class) where BarImpl will implement IBar interface. Problem is classes which implement that interface (for instance BarImpl) are in another maven module where I don't have access from current module so I cannot use one of this impl classes in that annotation. Can you tell me if there is another solution?
Thank you in advice.
Are you sure you mean deserialization? You'll need a concrete implementation of your interface if Jackson's going to be able to create Java objects for you.
deserialization = Json String -> Java object
serialization = Java object -> Json String
When serializing Jackson will use the runtime class of the object, so it will use the actual implementations rather than attempt to use the interface. If you want to customize this you can add a serializer for your interface. You'll need to decide exactly what you want to write out.
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(IBar.class, new JsonSerializer<IBar>() {
#Override
public void serialize(IBar value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeStringField("fieldName", value.getFieldName());
gen.writeEndObject();
}
});
objectMapper.registerModule(module);
I'm using Jackson for JSON serialization, and I would like to override the null serializer -- specifically, so that null values are serialized as empty strings in JSON rather than the string "null".
All of the documentation and examples I've found on how to set null serializers refers to Jackson 1.x -- for example, the code at the bottom of http://wiki.fasterxml.com/JacksonHowToCustomSerializers no longer compiles with Jackson 2.0 because StdSerializerProvider no longer exists in the library. That web page describes Jackson 2.0's module interface, but the module interface has no obvious way to override the null serializer.
Can anyone provide a pointer on how to override the null serializer in Jackson 2.0?
Override the JsonSerializer serialize method as below.
public class NullSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// any JSON value you want...
jgen.writeString("");
}
}
then you can set NullSerializer as default for custom object mapper:
public class CustomJacksonObjectMapper extends ObjectMapper {
public CustomJacksonObjectMapper() {
super();
DefaultSerializerProvider.Impl sp = new DefaultSerializerProvider.Impl();
sp.setNullValueSerializer(new NullSerializer());
this.setSerializerProvider(sp);
}
}
or specify it for some property using #JsonSerialize annotation, e.g:
public class MyClass {
#JsonSerialize(nullsUsing = NullSerializer.class)
private String property;
}
I was not able to get the accepted answer to work for me. Perhaps because my ObjectMapper is a Spring Bean in my environment.
I reverted by to using a SimpleModule.
Same serializer:
public class NullSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// any JSON value you want...
jgen.writeString("");
}
}
The annotation is located in a Mixin as I don't have access to modifying MyClass:
public abstract class MyClassMixin {
#JsonSerialize(nullsUsing = NullSerializer.class)
public String property;
}
To attach the serializer to my mapper, I use a module in my Spring component:
#AutoWired
ObjectMapper objectMapper;
#PostConstruct
public void onPostConstruct() {
SimpleModule module = new SimpleModule();
module.setMixInAnnotation(MyClass.class, MyClassMixin.class);
objectMapper.registerModule(module);
}
I have a a map that looks like this:
public class VerbResult {
#JsonProperty("similarVerbs")
private Map<Verb, List<Verb>> similarVerbs;
}
My verb class looks like this:
public class Verb extends Word {
#JsonCreator
public Verb(#JsonProperty("start") int start, #JsonProperty("length") int length,
#JsonProperty("type") String type, #JsonProperty("value") VerbInfo value) {
super(length, length, type, value);
}
//...
}
I want to serialize and deserialize instances of my VerbResult class, but when I do I get this error: Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]
I read online that you need to tell Jackson how to deserialize map keys, but I didn't find any information explaining how to go about doing this. The verb class needs to be serialized and deserialzed outside of the map as well, so any solution should preserve this functionality.
Thank you for your help.
After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add the #JsonDeserialize(keyUsing = YourCustomDeserializer.class) annotation to the map. Then implement your custom deserializer by extending KeyDeserializer and override the deserializeKey method. The method will be called with the string key and you can use the string to build the real object, or even fetch an existing one from the database.
So first in the map declaration:
#JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;
Then create the deserializer that will be called with the string key.
public class MyCustomDeserializer extends KeyDeserializer {
#Override
public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
//Use the string key here to return a real map key object
return mapKey;
}
}
Works with Jersey and Jackson 2.x
As mentioned above the trick is that you need a key deserializer (this caught me out as well). In my case a non-String map key was configured on my class but it wasn't in the JSON I was parsing so an extremely simple solution worked for me (simply returning null in the key deserializer).
public class ExampleClassKeyDeserializer extends KeyDeserializer
{
#Override
public Object deserializeKey( final String key,
final DeserializationContext ctxt )
throws IOException, JsonProcessingException
{
return null;
}
}
public class ExampleJacksonModule extends SimpleModule
{
public ExampleJacksonModule()
{
addKeyDeserializer(
ExampleClass.class,
new ExampleClassKeyDeserializer() );
}
}
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );
Building on the answer given here that suggests to implement a Module with a deserializer. The JodaTime Module is an easy to understand full example of a module containing serializers and deserializers.
Please note that the Module feature was introduced in Jackson version 1.7 so you might need to upgrade.
So step by step:
create a module containing a (de)serializer for your class based on the Joda example
register that module with mapper.registerModule(module);
and you'll be all set
Assuming we have a Map property, like the following:
class MyDTO{
#JsonSerialize(keyUsing = MyObjectKeySerializer.class)
#JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
private Map<MyObjectKey , List<?>> map;
}
We serilize the MyObjectKey as a json string, while call objectMapper.writeAsString;
And deserilize from the json string,to MyObjectKey
public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {
public Serializer() {
super(MyObjectKey.class);
}
#Override
public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeFieldName(JsonUtil.toJSONString(value));
}
}
public class MyObjectKeyDeserializer extends KeyDeserializer {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return JsonUtil.toObject(key, MyObjectKey.class);
}
}
After scouring the web, I think I have a decent solution for how to handle POJO-style keys (although, as always, you are best served not using a full object as a map key).
Serializer (registered as a Jackson module, inside of Spring Boot):
#Bean
fun addKeySerializer(): Module =
SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())
class YourClassSerializer() : JsonSerializer<YourClass>() {
override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
}
}
(note that, in a standard Java environment, you will have to instantiate your own objectMapper instance here)
Deserializer:
#Bean
fun addKeyDeserializer(): Module =
SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())
class YourClassDeserializer() : KeyDeserializer() {
override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
return ctxt.parser.readValueAs(YourClass::class.java)
}
}