Jackson JSON custom serialization for certain fields

JavaJsonSerializationJackson

Java Problem Overview


Is there a way using Jackson JSON Processor to do custom field level serialization? For example, I'd like to have the class

public class Person {
    public String name;
    public int age;
    public int favoriteNumber;
}

serialized to the follow JSON:

{ "name": "Joe", "age": 25, "favoriteNumber": "123" }

Note that age=25 is encoded as a number while favoriteNumber=123 is encoded as a string. Out of the box Jackson marshalls int to a number. In this case I want favoriteNumber to be encoded as a string.

Java Solutions


Solution 1 - Java

You can implement a custom serializer as follows:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = IntToStringSerializer.class, as=String.class)
    public int favoriteNumber:
}

 
public class IntToStringSerializer extends JsonSerializer<Integer> {
  
    @Override
    public void serialize(Integer tmpInt, 
                          JsonGenerator jsonGenerator, 
                          SerializerProvider serializerProvider) 
                          throws IOException, JsonProcessingException {
        jsonGenerator.writeObject(tmpInt.toString());
    }
}

Java should handle the autoboxing from int to Integer for you.

Solution 2 - Java

Jackson-databind (at least 2.1.3) provides special ToStringSerializer (com.fasterxml.jackson.databind.ser.std.ToStringSerializer)

Example:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = ToStringSerializer.class)
    public int favoriteNumber:
}

Solution 3 - Java

jackson-annotations provides @JsonFormat which can handle a lot of customizations without the need to write the custom serializer.

For example, requesting a STRING shape for a field with numeric type will output the numeric value as string

public class Person {
    public String name;
    public int age;
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    public int favoriteNumber;
}

will result in the desired output

{"name":"Joe","age":25,"favoriteNumber":"123"}

Solution 4 - Java

Add a @JsonProperty annotated getter, which returns a String, for the favoriteNumber field:

public class Person {
    public String name;
    public int age;
    private int favoriteNumber;
    
    public Person(String name, int age, int favoriteNumber) {
    	this.name = name;
    	this.age = age;
    	this.favoriteNumber = favoriteNumber;
    }
    
    @JsonProperty
    public String getFavoriteNumber() {
    	return String.valueOf(favoriteNumber);
    }

	public static void main(String... args) throws Exception {
    	Person p = new Person("Joe", 25, 123);
    	ObjectMapper mapper = new ObjectMapper();
    	System.out.println(mapper.writeValueAsString(p)); 
        // {"name":"Joe","age":25,"favoriteNumber":"123"}
    }
}

Solution 5 - Java

In case you don't want to pollute your model with annotations and want to perform some custom operations, you could use mixins.

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Person.class, PersonMixin.class);
mapper.registerModule(simpleModule);

Override age:

public abstract class PersonMixin {
    @JsonSerialize(using = PersonAgeSerializer.class)
    public String age;
}

Do whatever you need with the age:

public class PersonAgeSerializer extends JsonSerializer<Integer> {
    @Override
    public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(String.valueOf(integer * 52) + " months");
    }
}

Solution 6 - Java

with the help of @JsonView we can decide fields of model classes to serialize which satisfy the minimal criteria ( we have to define the criteria) like we can have one core class with 10 properties but only 5 properties can be serialize which are needful for client only

Define our Views by simply creating following class:

public class Views
{
	static class Android{};
	static class IOS{};
	static class Web{};
}

Annotated model class with views:

public class Demo 
{
	public Demo() 
	{
	}

@JsonView(Views.IOS.class)
private String iosField;

@JsonView(Views.Android.class)
private String androidField;

@JsonView(Views.Web.class)
private String webField;

 // getters/setters
...
..
}

Now we have to write custom json converter by simply extending HttpMessageConverter class from spring as:

    public class CustomJacksonConverter implements HttpMessageConverter<Object> 
    {
    public CustomJacksonConverter() 
    	{
    		super();
		//this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
		this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
		this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);

	}
	
	// a real message converter that will respond to methods and do the actual work
	private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();

	@Override
	public boolean canRead(Class<?> clazz, MediaType mediaType) {
		return delegate.canRead(clazz, mediaType);
	}

	@Override
	public boolean canWrite(Class<?> clazz, MediaType mediaType) {
		return delegate.canWrite(clazz, mediaType);
	}

	@Override
	public List<MediaType> getSupportedMediaTypes() {
		return delegate.getSupportedMediaTypes();
	}

	@Override
	public Object read(Class<? extends Object> clazz,
			HttpInputMessage inputMessage) throws IOException,
			HttpMessageNotReadableException {
		return delegate.read(clazz, inputMessage);
	}

	@Override
	public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
	{
		synchronized(this) 
		{
			String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
			if ( userAgent != null ) 
			{
				switch (userAgent) 
				{
				case "IOS" :
					this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
					break;
				case "Android" :
					this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
					break;
				case "Web" :
					this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
					break;
				default:
					this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
					break;
				}
			}
			else
			{
				// reset to default view
				this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
			}
			delegate.write(obj, contentType, outputMessage);
		}
	}

}

Now there is need to tell spring to use this custom json convert by simply putting this in dispatcher-servlet.xml

<mvc:annotation-driven>
		<mvc:message-converters register-defaults="true">
			<bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter" >
			</bean>
		</mvc:message-converters>
	</mvc:annotation-driven>

That's how you will able to decide which fields to get serialize.

Solution 7 - Java

You can create a custom serializer inline in the mixin. Then annotate a field with it. See example below that appends " - something else " to lang field. This is kind of hackish - if your serializer requires something like a repository or anything injected by spring, this is going to be a problem. Probably best to use a custom deserializer/serializer instead of a mixin.

package com.test;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.test.Argument;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//Serialize only fields explicitly mentioned by this mixin.
@JsonAutoDetect(
    fieldVisibility = Visibility.NONE,
    setterVisibility = Visibility.NONE,
    getterVisibility = Visibility.NONE,
    isGetterVisibility = Visibility.NONE,
    creatorVisibility = Visibility.NONE
)
@JsonPropertyOrder({"lang", "name", "value"})
public abstract class V2ArgumentMixin {

  @JsonProperty("name")
  private String name;

  @JsonSerialize(using = LangCustomSerializer.class, as=String.class)
  @JsonProperty("lang")
  private String lang;

  @JsonProperty("value")
  private Object value;


  
  public static class LangCustomSerializer extends JsonSerializer<String> {

    @Override
    public void serialize(String value,
                          JsonGenerator jsonGenerator,
                          SerializerProvider serializerProvider)
        throws IOException, JsonProcessingException {
      jsonGenerator.writeObject(value.toString() + "  - something else");
    }
  }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSteve KuoView Question on Stackoverflow
Solution 1 - JavaKevin BowersoxView Answer on Stackoverflow
Solution 2 - JavawerupokzView Answer on Stackoverflow
Solution 3 - JavaOleg EstekhinView Answer on Stackoverflow
Solution 4 - JavaJoão SilvaView Answer on Stackoverflow
Solution 5 - JavaIgor G.View Answer on Stackoverflow
Solution 6 - JavaChetan PardeshiView Answer on Stackoverflow
Solution 7 - JavaVladimirView Answer on Stackoverflow