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

1.8k views Asked by At

For example I have a POJO defined as below with jackson-core and jackson-databind (version 2.8.3) annotations omitting getters and setters for brevity.

class Sample {
     private String foo;
     private String bar;
     private Map<String, Map<String, Object>> data;
}

and I would like to write a custom serializer that takes above POJO and generates

{
     "foo":"val",
     "bar":"val2",
     "data_1": {
          "someInt":1
     },
     "data_2": {
          "someBoolean":true
     }
}

Here data_1 and data_2 are keys of main Map and their inner attributes are made up of their sub map (nested map). Also, the actual property data shouldn't be present in resulting JSON at all.

Please note that foo and bar are example of fields, actually the pojo has 15+ fields.

3

There are 3 answers

0
techno shaft On BEST ANSWER

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

1
naib khan On

please correct your class variable data, it should be like this

private Map<String,Map<String, Object>> data; 

then write a method to serialize like this :

public static void serializeSample() {
    ObjectMapper mapper = new ObjectMapper();
    Sample sample=new Sample();
    sample.setBar("val2");
    sample.setFoo("val");
    Map<String, Map<String, Object>> sampleData=new HashMap<>();
    Map<String, Object> data_3=new HashMap<>();
    Map<String, Object> data_4=new HashMap<>();
    data_3.put("someInt", 1);
    data_4.put("someBoolean", Boolean.TRUE);

    sampleData.put("data_1", data_3);
    sampleData.put("data_2", data_4);
    sample.setData(sampleData);

    try {
        mapper.writeValue(new File("log.txt"), sample);
    } catch (JsonGenerationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JsonMappingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }



}

this should work fine I think

2
Manos Nikolaidis On

You will need a custom serializer for that, that iterates over the parent Map. For each nested Map you can simply use writeObjectField with key, value as field name and value.

You also require that all other fields are serialized automatically. You can set a custom serializer on a field only, but you would still get a data field in the JSON. You actually want to elevate the contents of data so that they look like fields of Sample and that requires a custom serializer for Sample. The only way I can think of to automatically serialize everything else except the data field is to use reflection

The following serializer will produce JSON as in your question:

class SampleSerializer extends StdSerializer<Sample> {
    private static final List<Field> sampleFields;

    public SampleSerializer() { this(null); }
    private SampleSerializer(Class<Sample> t) { super(t); }

    static {
        sampleFields = Arrays.stream(Sample.class.getDeclaredFields())
                .filter(f -> !("data".equals(f.getName())))
                .collect(toList());
    }

    @Override
    public void serialize(Sample sample, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        jgen.writeStartObject();
        for (Field field : sampleFields) {
            try {
                field.setAccessible(true);
                jgen.writeObjectField(field.getName(), field.get(sample));
            } catch (IllegalAccessException ignored) {
            }
        }
        for (Entry<String, Map<String, Object>> entry : sample.getData().entrySet()) {
            jgen.writeObjectField(entry.getKey(), entry.getValue());
        }
        jgen.writeEndObject();
    }
}

You must also specify that SampleSerializer will be used for Sample, for example by annotating the class:

@JsonSerialize(using = SampleSerializer.class)
class Sample {