I am trying to implement generic Spring trim serializer across application however its doesn't seems to be working.
And if I manually put this serializer #JsonSerialize(using = StringTrimmerSerializer.class) on a particular field it does work not sure what i need to do to make it work throughout application without putting it for all fields individually
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.jackson.JsonComponent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
#JsonComponent
public class StringTrimmerSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (!StringUtils.isEmpty(value)) {
value = value.trim();
}
gen.writeString(value);
}
}
Update:
Tried registering serializer as well but same issue
#Configuration
public class JacksonConfiguration {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper(); //
//mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
mapper.registerModule(new SimpleModule().addSerializer(String.class, new StringTrimmerSerializer()));
return mapper;
}
/*
* #Bean public Module customSerializer() { SimpleModule module = new
* SimpleModule(); module.addSerializer(String.class, new
* StringTrimmerSerializer()); return module; }
*/
}
Main Class package : com.demo
Serializer Package : com.demo.config
Spring boot Version - 2.2.5.RELEASE
Jackson-databind - 2.10.2
Add constructors in StringTrimmerSerializer
public StringTrimmerSerializer ()
public StringTrimmerSerializer (Class<String> s) {
super(s);
}
I was able to resolve by registering custom serializer to jaskcosn's default object mapper rather than creating a new reference of ObjectMapper.
#Configuration
public class JacksonConfiguration extends WebMvcConfigurationSupport {
#Autowired
private ObjectMapper objectMapper;
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
objectMapper.registerModule(new SimpleModule().addSerializer(String.class, new StringTrimmerSerializer()));
converter.setObjectMapper(objectMapper);
return converter;
}
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
}
Related
I have JSON that looks something like this (the full JSON is much larger):
{
"legalLastName": "lastName",
"legalFirstName": "firstName",
"terminationDate": null,
"collegeEducation": [{
"major": "Finance",
"school": "Towson University",
"quarter": null,
"degreeType": "B.S.",
"yearEarned": "1990",
"degreeLevel": "Undergraduate"
}]
}
How do I use the ObjectMapper to apply custom serialization to collegeEducation? I can't use the annotation JsonSerializer, because the POJO is generated, and that library doesn't provide a way to apply that annotation. So I'd like to use the default ObjectMapper serialization for all fields but the list.
Objective: A field in Education named as degreeType will be serialized to json field degree
Mixin or Custom serializer can be used
Student and Education class
Lets define the Student and Education class as follows
static class Education {
String major;
String degreeType;
}
static class Student {
String legalLastName;
String legalFirstName;
List<Education> educationList;
}
Mixin
Use mixin to define a parallel class with updated field specifications
Mixin class for Education
Define the field in the actual object String degreeType;
Add the custom field name to be serialized to #JsonProperty("degree")
abstract class EducationMixin {
#JsonProperty("degree")
String degreeType;
}
Configure ObjectMapper with Mixin
Enable field visibility(as the class does not have getter/setter)
Register the education mixin
static void withMixin(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
studentMapper = studentMapper.addMixIn(Education.class, EducationMixin.class);
System.out.println(studentMapper.writeValueAsString(student));
}
Or Custom Serializer
Custom serializer's can be used to serialize java objects.
Add custom serializer for Education
Add the custom serializer class to Serialize Education
static class EducationSerializer extends JsonSerializer<Education> {
#Override
public void serialize(Education education, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeStringField("major", education.major);
gen.writeStringField("degree", education.degreeType);
gen.writeEndObject();
}
}
Initialize ObjectMapper with necessary configuration
Enable field visibility(as the class does not have getter/setter)
Register the education serializer
static void withCustom(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
SimpleModule studentModule = new SimpleModule();
studentModule.addSerializer(Education.class, new EducationSerializer());
studentMapper.registerModule(studentModule);
System.out.println(studentMapper.writeValueAsString(student));
}
Full working code
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MixinTest {
public static void main(String[] args) throws Exception {
Student student = new Student();
student.legalFirstName = "first";
student.legalLastName = "last";
Education education = new Education();
education.degreeType = "degreetypevalue";
education.major = "majorvalue";
student.educationList = new ArrayList<>();
student.educationList.add(education);
withMixin(student);
withCustom(student);
}
static void withMixin(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
studentMapper = studentMapper.addMixIn(Education.class, EducationMixin.class);
System.out.println(studentMapper.writeValueAsString(student));
}
static void withCustom(Student student) throws Exception {
ObjectMapper studentMapper = new ObjectMapper();
studentMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
SimpleModule studentModule = new SimpleModule();
studentModule.addSerializer(Education.class, new EducationSerializer());
studentMapper.registerModule(studentModule);
System.out.println(studentMapper.writeValueAsString(student));
}
static class Education {
String major;
String degreeType;
}
static class Student {
String legalLastName;
String legalFirstName;
List<Education> educationList;
}
abstract class EducationMixin {
#JsonProperty("degree")
String degreeType;
}
static class EducationSerializer extends JsonSerializer<Education> {
#Override
public void serialize(Education education, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeStringField("major", education.major);
gen.writeStringField("degree", education.degreeType);
gen.writeEndObject();
}
}
}
I'm trying to figure how to explain Jersey and Jackson how to deserialize a Future that I pass as byte[].
I create my own ContextResolver
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private static ObjectMapper mapper = null;
public ObjectMapperContextResolver() {
mapper = ObjectMapperFactory.getObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
And the implementation of the ObjectMapper
public static ObjectMapper getObjectMapper() {
ObjectMapper defaultObjectMapper = new ObjectMapper();
SimpleModule futureModule = new SimpleModule("FutureModule");
futureModule.<Future>addDeserializer(Future.class, new FutureDeserializer<String>());
defaultObjectMapper.registerModule(futureModule);
return defaultObjectMapper;
}
And then finally in the implementation of my FutureDeserializer
public class FutureDeserializer<T> extends StdDeserializer<Future<T>>{
public FutureDeserializer() {
super(Future.class);
}
#Override
public Future<T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper=(ObjectMapper)jp.getCodec();
//TODO: Breakpoint never stop here
return null;
}
}
Then I register in my ResourceConfig before start the JerseyTest
ResourceConfig rc = new ResourceConfig();
rc.register(SpringLifecycleListener.class);
rc.register(RequestContextFilter.class);
rc.register(new JacksonFeature());
rc.register(new ObjectMapperContextResolver());
But when I run the test the ObjectMapperContextResolver is invoked and the mapper returned to Jersey, but he never use the FutureDeserializer.
Any idea what I´m doing wrong?
How do I force null objects to be serialized as an empty string in my default JSON responses in Spring Boot?
I would like it to show as:
{
myProperty: "",
}
But what comes out by default is:
{
myProperty: null,
}
I don't want myProperty to be excluded from this list, so I'm not interested in changing the JsonInclude.Include.NON_NULL
Here is what I've tried:
In my main WebMvcConfigurerAdapter class:
#Autowired
private NullAsEmptyStringSerializer nullSerializer;
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomSerialization() {
return jacksonObjectMapperBuilder -> {
DefaultSerializerProvider serializerProvider = new DefaultSerializerProvider.Impl();
serializerProvider.setNullValueSerializer(nullSerializer);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializerProvider(serializerProvider);
jacksonObjectMapperBuilder.configure(objectMapper);
};
And then my nullSerializer object class:
#Component
public class NullAsEmptyStringSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("\"\"");
}
}
The problem is that the serialize() method is never called on my custom class.
The problem with this configuration through Jackson2ObjectMapperBuilderCustomizer lies in these lines:
ObjectMapper objectMapper = new ObjectMapper();
jacksonObjectMapperBuilder.configure(objectMapper);
In fact, this has no effect, because the builder always uses its own instance of ObjectMapper, created internally. If we check in the source code of the builder:
public <T extends ObjectMapper> T build() {
ObjectMapper mapper;
// conditional instantiation of mapper
configure(mapper);
return (T) mapper;
}
Our previous instance of ObjectMapper is just ignored.
Unfortunately, there is no way to set SerializerProviders through Jackson2ObjectMapperBuilder. Probably, this feature is not yet implemented in the API.
But there are at least two possible solutions for the problem.
First, according to the official Spring docs, you can always create your own ObjectMapper bean marked with #Primary and configured fully as you wish, for example:
#Bean
#Primary
public ObjectMapper objectMapper() {
DefaultSerializerProvider serializerProvider = new DefaultSerializerProvider.Impl();
serializerProvider.setNullValueSerializer(nullSerializer);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializerProvider(serializerProvider);
return objectMapper;
}
This will enable the nullSerializer properly, but the drawback is that Spring's auto-configuration of the ObjectMapper instance will be lost and that is probably not what you always want.
The second solution goes through bootstrapping the default instance of ObjectMapper and setting the desired property on it. This can be achieved by implementing the InitializingBean interface, in quite an easy way:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ObjectMapperConfig implements InitializingBean {
#Autowired
private NullAsEmptyStringSerializer nullSerializer;
#Autowired
private ObjectMapper objectMapper;
// will be called by Spring after all the beans are created
// and the proper `objectMapper` instance is available here.
#Override
public void afterPropertiesSet() throws Exception {
DefaultSerializerProvider serializerProvider = new DefaultSerializerProvider.Impl();
serializerProvider.setNullValueSerializer(nullSerializer);
objectMapper.setSerializerProvider(serializerProvider);
}
}
Last but not least, there is a small problem in the NullAsEmptyStringSerializer above.
jsonGenerator.writeString("\"\"");
will output "myProperty":"\"\"". This should be changed to
jsonGenerator.writeString("");
I don't have option of spring.xml so i went by annotated method.
I have below REST Interfaces in package : com.dpk.cm.impl.ecommerce.rest
and implementation in com.dpk.cm.impl.ecommerce.rest.services
I created one spring config class: but seems like i am still seeing in my JSON response empty objects.
Below is my code :
#Configuration
#ComponentScan(basePackages = "com.dpk.cm.impl.ecommerce.rest")
#EnableWebMvc
public class SpringConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setSerializationInclusion(Inclusion.NON_EMPTY);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
}
How to remove the Empty Objects from the JSON Reponse Object.
I had similar requirement, but though I use CXF framework on spring boot, there spring boot was creating ObjectMapper which was overriding configuration. Hence I was manually create ObjectMapper as shown below.
#Bean(name = "objectMapper")
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(
SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, false);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
true);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
You can create your custom serializer where you can add a condition on serialization of the object.
Model
#JsonSerialize(using = IgnoreEmptyPersonSerializer.class)
public class Person {
private String name;
private String address;
public Person(String name, String address){
this.name = name;
this.address = address;
}
...
//setters and getters
...
}
Custom Serializer
public class IgnoreEmptyPersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String name = value.getName();
String address = value.getAddress();
//Dont serialize it if it is empty
if((name == null || name.trim().equals("")) &&
(address == null || address.trim().equals(""))){
return;
}
jgen.writeStartObject();
jgen.writeFieldName("name");
jgen.writeString(value.getName());
jgen.writeFieldName("address");
jgen.writeString(value.getAddress());
jgen.writeEndObject();
}
}
I've got a class that configures a Jackson ObjectMapper. It adds in some custom serializers and deserializers for my object types as follows:
public class JsonMapperFactory {
public static ObjectMapper createObjectMapper() {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
addCustomDeserializersTo(module);
addCustomSerializersTo(module);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
private static void addCustomSerializersTo(final SimpleModule module) {
module.addSerializer(DateTime.class, new DateTimeSerializer());
}
private static void addCustomDeserializersTo(final SimpleModule objectMapper) {
objectMapper.addDeserializer(DateTime.class, new DateTimeDeserializer());
}
}
I've tested my customer serializers within their own test classes, so in my test of this JsonMapperFactory class, I'm trying to simply check that the ObjectMapper created has the expected serializers (or deserializers) This could be achieve by introspecting the ObjectMapper, but it doesn't seem to have any mechanisms to do this.
Does anyone know of a nice way to test that?
For deserializers, I have the following:
private void assertThatObjectMapperUsesCorrectDeserializer(final Class<?> typeClazz, final Class<?> deserializerClazz) throws JsonMappingException {
final DeserializationConfig deserializationConfig = this.objectMapper.getDeserializationConfig();
final JsonDeserializer<Object> deserializer = this.objectMapper.getDeserializerProvider().findTypedValueDeserializer(deserializationConfig, javaTypeFor(typeClazz), null);
assertThat(deserializer, is(instanceOf(deserializerClazz)));
}
private JavaType javaTypeFor(final Class<?> clazz) {
return TypeFactory.type(clazz); //deprecated method :(
}
Which is quite verbose and uses deprecated methods.
I'm yet to find a way to do a similar test for the serializers. So I've currently resorted to serializing an object and check it serializes correctly (essentially duplicating the serializer test)
Any ideas are very welcome.
From the answers & comments provided here, I recently redesigned the class to use builders for both the Module and the ObjectMapper. This allowed me to provide mocks and check that the correct (de)serializers were added to the module and then the module is registered to the object mapper as expected.
Object Mapper Builder:
public class ObjectMapperBuilder {
ObjectMapper mapper;
public ObjectMapperBuilder configure(final ObjectMapper mapper) {
this.mapper = mapper;
return this;
}
public ObjectMapperBuilder withModule(final Module module) {
this.mapper.registerModule(module);
return this;
}
public ObjectMapper build() {
return this.mapper;
}
}
Module Builder:
public class SimpleModuleBuilder {
SimpleModule module;
public SimpleModuleBuilder configure(final SimpleModule module) {
this.module = module;
return this;
}
public <X> SimpleModuleBuilder withSerializer(final Class<X> clazz, final JsonSerializer<X> serializer) {
this.module.addSerializer(clazz, serializer);
return this;
}
public <X> SimpleModuleBuilder withDeserializer(final Class<X> clazz, final JsonDeserializer<X> deserializer) {
this.module.addDeserializer(clazz, deserializer);
return this;
}
public SimpleModule build() {
return this.module;
}
}
And finally, the new JsonMapperFactory:
public class JsonMapperFactory {
public static ObjectMapper configureObjectMapper(final ObjectMapper mapper, final SimpleModule module) {
final SimpleModuleBuilder modulebuilder = new SimpleModuleBuilder();
final SimpleModule configuredModule = modulebuilder.configure(module)
.withSerializer(DateTime.class, new DateTimeSerializer())
.withDeserializer(DateTime.class, new DateTimeDeserializer())
.build();
final ObjectMapperBuilder objectMapperBuilder = new ObjectMapperBuilder();
return objectMapperBuilder.configure(mapper).withModule(configuredModule).build();
}
}
The factory method is still used within Spring configuration, but the configuration now instantiates the blank Module and ObjectMapper before providing them to the factory methods that then configure them.
If JsonDeserializer (and DateTimeDeserializer too) was an interface, you could easily "JMock" it, pass mocked instance to JsonMapperFactory#createObjectMapper and then expect exactly 1 invocation of your custom "serialize" method; e.g.
DateTimeSerializer serializer = context.mock(DateTimeSerializer.class);
DateTimeDeserializer serializer = context.mock(DateTimeDeserializer.class);
ObjectMapper mapper = JacksonMapperFactory.createObjectMapper(deserializer, serializer);
exactly(1).of(jsonDeserializer).serialize(myDateTime,
with(any(JsonGenerator.class),
with(any(SerializerProvider.class)))
Being a concrete class, you can instead define a new (test-scoped) De/Serializer that extends your custom DateTime(De)serializer, and simply count invocation on that:
private static class DateTimeDeserializerWithCounter extends DateTimeDeserializer {
public int counter = 0;
#Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
counter++;
return super.deserialize(jsonParser, deserializationContext);
}
}
#Test
public void usageTest(){
//init mapper with the above DateTimeDeserializerWithCounter - see below
mapper.readValue("...", DateTime.class);
Assert.assertEquals(1, deserializer.counter);
}
Below a snapshot of a more "test-oriented" Factory:
//package visibility, to allow passing different De/Serializers while testing
static ObjectMapper createObjectMapper(JsonDeserializer deserializer, JsonSerializer serializer) {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
module.addDeserializer(DateTime.class, deserializer);
module.addSerializer(DateTime.class, serializer);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
//production method: no-args, as in the original version
public static ObjectMapper createObjectMapper() {
return createObjectMapper(new DateTimeDeserializer(), new DateTimeSerializer());
}
Hope that helps.