Jackson will give you null for other objects, but for String it will give empty String.
But you can use a Custom JsonDeserializer to do this:
class CustomDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
if (node.asText().isBlank()) {
return null;
}
return node.toString();
}
}
In class you have to use it for location field:
class EventBean {
public Long eventId;
public String title;
@JsonDeserialize(using = CustomDeserializer.class)
public String location;
}
Answer from Sachin Gupta on Stack OverflowJackson will give you null for other objects, but for String it will give empty String.
But you can use a Custom JsonDeserializer to do this:
class CustomDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
if (node.asText().isBlank()) {
return null;
}
return node.toString();
}
}
In class you have to use it for location field:
class EventBean {
public Long eventId;
public String title;
@JsonDeserialize(using = CustomDeserializer.class)
public String location;
}
It is possible to define a custom deserializer for the String type, overriding the standard String deserializer:
this.mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String result = StringDeserializer.instance.deserialize(p, ctxt);
if (StringUtils.isEmpty(result)) {
return null;
}
return result;
}
});
mapper.registerModule(module);
This way all String fields will behave the same way.
You will have to write a custom Jackson Serializer - a good example is here - http://wiki.fasterxml.com/JacksonHowToCustomSerializers (there is a specific example of how to convert null values to empty Strings that you can use)
Here are all the steps(for Jackson < 2.0):
Write your custom null Serializer:
import java.io.IOException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class NullSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString("");
}
}
Register this with Jackson Objectmapper:
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ser.StdSerializerProvider;
public class CustomObjectMapper extends ObjectMapper{
public CustomObjectMapper(){
StdSerializerProvider sp = new StdSerializerProvider();
sp.setNullValueSerializer(new NullSerializer());
this.setSerializerProvider(sp);
}
}
Register this objectmapper with Spring MVC:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper">
<bean class="CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
I have also faced the same problem in my project and I have therefore quickly come up with a solution for the same. This post will surely help all those who have been struggling with the same issue.
Step 1:- Create your Custom Null Handler Serializer.
public class NullSerializer extends StdSerializer<Object> {
public NullSerializer(Class<Object> t) {
super(t);
}
public NullSerializer() {
this(null);
}
@Override
public void serialize(Object o, com.fasterxml.jackson.core.JsonGenerator jsonGenerator, com.fasterxml.jackson.databind.SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
}
Step 2:- Create a bean of MappingJackson2HttpMessageConverter.
@Bean
@Primary
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
mapper.getSerializerProvider().setNullValueSerializer(new NullSerializer());
jsonConverter.setObjectMapper(mapper);
return jsonConverter;
}
Thank you for taking some time out to read this post. I hope that this was able to resolve your queries to some extent.
You can override default ObjectMapper (provided by Spring Boot auto-configuration) and configure globally format to use for properties of type String.
@Configuration
public class JacksonConfiguration {
@Bean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
final var objectMapper = builder.createXmlMapper(false).build();
objectMapper.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
return objectMapper;
}
}
DefaultSerializerProvider.Impl sp = new DefaultSerializerProvider.Impl();
sp.setNullValueSerializer(new NullSerializer());
new ObjectMapper().setSerializerProvider(sp)...
Where
public class NullSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
}
I finally found what was my issue.
In my application, I use a custom RestTemplate. But this CustomRestTemplate use the default constructor of the Spring RestTemplate class. So it use the default MessageConverter list.
The solution was to add a constructor for my CustomRestTemplate with the MessageConverter list as input.
@Component
public class CustomRestTemplate extends RestTemplate {
@Autowired
public CustomRestTemplate (List<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
}
And to configuration the converter with the disabled "ACCEPT_EMPTY_STRING_AS_NULL_OBJECT" feature :
@Configuration
@ComponentScan(basePackages = "com.geodis.rt")
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters( List<HttpMessageConverter<?>> converters ) {
converters.add(0, converter());
}
@Bean
MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.getObjectMapper().disable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
return converter;
}
}
Try disabling the ACCEPT_EMPTY_STRING_AS_NULL_OBJECT deserialization feature, but it should not be enabled by default so I would be surprised if this is the solution.
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class JacksonConfiguration {
@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToDisable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
return builder;
}
}
There are a couple of ways to achieve custom null value serialising:
If you want to serialise a null as an empty String, try using this annotation on a property, or setter:
@JsonSetter(nulls=Nulls.AS_EMPTY)
or the same for specific mapper:
MAPPER.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
- You can initialise properties with default values on the declaration or in the getter.
- As you've already mentioned, by providing a custom serialiser.
I did try your code, and that serialised null value as expected when using an ObjectMapper instead of JodaMapper. Is there any particular reason for using a JodaMapper?
I found the solution.... I had
@JsonInclude(JsonInclude.Include.NON_NULL)
at class level in the class that I wanted to serialize. When I remove the annotation I the code above works.
DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT is not going to help you because it means "deserialise empty string JSON value as null for object field" and unfortunately your String fields are scalars not JSON objects.
I think there is no convenient way to get behaviour you want. It is possible to override deserializaton behaviour for all fields with type String but you may not want it. Also you can define custom deserialzer but you will need to annotate every field like this:
@JsonDeserialize(using = MyStringDeseralizer.class)
private String businessTitle;
where class MyStringDeseralizer extends JsonDeserializer<String> and implement conversion logic you need.
Actually there is a better solution then having to put it on each attribute. Create a custom deserializer, EmptyToNullStringDeserializer, for changing '' to null. This is based on a co-workers solution.
builder
.deserializerByType(String.class, new EmptyToNullStringDeserializer());
You could enable ACCEPT_EMPTY_STRING_AS_NULL_OBJECT in your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
Alternatively, you could define a custom deserializer:
public class CustomStringDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String value = StringDeserializer.instance.deserialize(p, ctxt);
if (value == null || value.trim().isEmpty()) {
return null;
}
return value;
}
}
And register it to a module in your ObjectMapper:
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new CustomStringDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
This deserializer will be used to deserialize all strings.
Your @JsonDeserialize should be on field level not on class level. Add on every String field.