Jackson JSON Custom Serialization for Certain Fields

Jackson JSON custom serialization for certain fields

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.

Custom Jackson serializer on specific fields

The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use @JsonView and custom serializer, something like:

class Views {
public static class ShowSSN {}
}

private static class MyBean{
@JsonSerialize(using = MyBeanSerializer.class)
@JsonView(Views.ShowSSN.class)
String ssn;
//getter setter constructor
}

private class MyBeanSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Class<?> jsonView = serializers.getActiveView();
if (jsonView == Views.ShowSSN.class)
gen.writeString(value); // your custom serialization code here
else
gen.writeString("xxx-xx-xxxx");
}
}

And use it like:

public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyBean bean = new MyBean("123-45-6789");

System.out.println(mapper.writerWithView(Views.ShowSSN.class)
.writeValueAsString(bean));
// results in {"ssn":"123-45-6789"}

System.out.println(mapper.writeValueAsString(bean));
// results in {"ssn":"xxx-xx-xxxx"}
}

Also for example in spring it would be really easy to use

@Controller
public class MyController {
@GetMapping("/withView") // results in {"ssn":"123-45-6789"}
@JsonView(Views.ShowSSN.class)
public @ResponseBody MyBean withJsonView() {
return new MyBean("123-45-6789");
}

@GetMapping("/withoutView") // results in {"ssn":"xxx-xx-xxxx"}
public @ResponseBody MyBean withoutJsonView() {
return new MyBean("123-45-6789");
}

}

Jackson - custom serializer that overrides only specific fields

I faced the same issue, and I solved it with CustomSerializerFactory.

This approach allows you to ignore some specific field for either for all objects, or for specific types.

public class EntityCustomSerializationFactory extends CustomSerializerFactory {

//ignored fields
private static final Set<String> IGNORED_FIELDS = new HashSet<String>(
Arrays.asList(
"class",
"value",
"some"
)
);

public EntityCustomSerializationFactory() {
super();
}

public EntityCustomSerializationFactory(Config config) {
super(config);
}

@Override
protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
super.processViews(config, builder);

//ignore fields only for concrete class
//note, that you can avoid or change this check
if (builder.getBeanDescription().getBeanClass().equals(Entity.class)){
//get original writer
List<BeanPropertyWriter> originalWriters = builder.getProperties();

//create actual writers
List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();

for (BeanPropertyWriter writer: originalWriters){
String propName = writer.getName();

//if it isn't ignored field, add to actual writers list
if (!IGNORED_FIELDS.contains(propName)){
writers.add(writer);
}
}

builder.setProperties(writers);
}

}
}

And afterwards you can use it something like the following:

objectMapper.setSerializerFactory(new EntityCustomSerializationFactory());
objectMapper.writeValueAsString(new Entity());//response will be without ignored fields

Jackson Custom serializer for custom behaviour of some fields while having default behaviour for rest of the fields

I figured out a simpler way to do this without using the custom serializer; It was with @JsonAnyGetter and @JsonAnySetter. Here is a complete example. I am pasting an answer with respect to sample pasted here as it might be useful for others.

class Sample {
private String foo;
private String bar;

private Map<String, Map<String, Object>> data = new LinkedHashMap<>();

@JsonAnyGetter
public Map<String, Map<String, Object>> getData() {
return data;
}

@JsonAnySetter
public void set(String key, Map<String, Object>) {
data.put(key, object);
}
}

Example

Jackson Custom Serializer shows the same context for 2 different field during the Json Serialization

A. Create two Map serialisers where one creates outer object and another not

Pros:

  • Easy to implement
  • Easy to test
  • One class does exactly one thing
  • Map serialiser which does not create outer object could be replaced by custom Map serialiser (if possible)

Cons:

  • Could be problematic if they need to share state.
  • Possibly duplicated code

B. Implement ContextualSerializer interface

Pros:

  • Can be configured for every field separately
  • Can share state if needed. User control how many instances are created.

Cons:

  • Does more than 1 thing
  • Can be easily over complicated

Examples:

  • Need Jackson serializer for Double and need to specify precision at runtime
  • Jackson custom annotation for custom value serialization
  • Deserialize to String or Object using Jackson
  • Jackson - deserialize inner list of objects to list of one higher level

How can I configure a Jackson ObjectMapper to serialize certain fields of certain beans with a custom value if they are null?

This should do the trick:

private String thumbnailUrl = "http://my.default.url";

As the OP mentioned in the comments, the class comes from a third-party library, so modifying it (a'la this SO) or annotating (a'la this SO) is not an option. In this case you have two options left:

  • Patch the third-party lib (apparently you have access to the source ;))

  • Hack your way around it in the custom serializer (needs to be properly tested though, and this quick hack wouldn't work in multi-threaded environments)

As in:

@Override
public void serialize(Product value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
boolean revert = false;
if (value.thumbnailUrl == null) {
value.thumbnailUrl = "http://my.default.url";
revert = true;
}
jgen.writeObject(value);
if (revert) {
value.thumbnailUrl = null;
}
}

Prevent custom serialization of specific field

It's possible to get the name of currently serialized field by implementing ContextualSerializer. The default serialization is available through SerializerProvider. Try rewriting the serializer like this:

class DateSerializer extends JsonSerializer<Date> implements ContextualSerializer {
private boolean doCustom;

DateSerializer() {}

private DateSerializer(boolean doCustom) { this.doCustom = doCustom; }

@Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (doCustom) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(value);
gen.writeString(date);
} else {
provider.defaultSerializeDateValue(value, gen);
}
}

@Override
public JsonSerializer<Date> createContextual(SerializerProvider config, BeanProperty property) {
boolean doCustom = property == null || !"dateToBeDefaultSerialized".equals(property.getName());
return new DateSerializer(doCustom);
}
}

Jackson custom serializer by field name?

Have you ever considered Jackson mix-in annotations?

Jackson mix-in annotations

It's a great alternative when modifying the classes is not an option. You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment statically defined ones.

Define a mix-in annotation interface (class would do as well):

public interface EventMixIn {

@JsonProperty("representation")
@JsonSerialize(using = CustomSerializer.class)
Object getRepresentation();
}

Then configure ObjectMapper to use the defined interface as a mix-in for your POJO:

ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT)
.addMixIn(User.Event.class, EventMixIn.class);

Usage considerations

Here are some usage considerations:

  • All annotation sets that Jackson recognizes can be mixed in.
  • All kinds of annotations (member method, static method, field, constructor annotations) can be mixed in.
  • Only method (and field) name and signature are used for matching annotations: access definitions (private, protected, ...) and method implementations are ignored.

For more details, you can have a look at this page.



Related Topics



Leave a reply



Submit