I’m using jackson-databind version 2.12.3 to serialize the return of an object that should return like this:
{ "field1":"value1", "field2":"value2", "links":{ "field":{ "href":"/link" }, "test":{ "href":"/test" } } }
My classes are these:
public class HrefType { private String href = null; ... }
public class Link extends HashMap<String, HrefType> { private HrefType field = null; ... }
public class MyObject { private String field1 = null; private String field2 = null; private Link links = null; ... }
The return is myObject:
MyObject myObject = new MyObject(); myObject.setField1("value1"); myObject.setField2("value2"); Link link = new Link(); link.setField(new HrefType().href("/link")); link.put("test",new HrefType().href("/test")); myObject.setLinks(link);
However with the default ObjectMapper the “link.setField” is ignored and the returned json is:
{ "field1":"value1", "field2":"value2", "links":{ "test":{ "href":"/test" } } }
I tried doing some tests with JsonSerializer but couldn’t do something generic for all classes that extend HashMap (these classes are generated from BerlinGroup’s PSD2 YAML, so I wouldn’t want to change the generated class).
Is there a generic way to do it, or should I make a serialize class for each class that extends the HashMap?
Advertisement
Answer
based on this answer I developed this generic method of making for all objects that extend a Map:
import java.io.IOException; import java.lang.reflect.Field; import java.util.Map; import org.springframework.util.ReflectionUtils; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class MyClassSerializer extends JsonSerializer<Object> { private final JsonSerializer<Object> defaultSerializer; public MyClassSerializer(JsonSerializer<Object> defaultSerializer) { this.defaultSerializer = (defaultSerializer); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void serialize(Object src, JsonGenerator gen, SerializerProvider provider) throws IOException { Field[] fields = src.getClass().getDeclaredFields(); for (Field field : fields) { try { boolean fieldAccessible = field.isAccessible(); field.setAccessible(true); Object object = ReflectionUtils.getField(field, src); if (object != null && object instanceof Map) { Field[] fieldsMap = object.getClass().getDeclaredFields(); Map map = (Map) object; for (Field fieldMap : fieldsMap) { boolean fieldMapAccessible = fieldMap.isAccessible(); fieldMap.setAccessible(true); Object fieldObject = ReflectionUtils.getField(fieldMap, object); if (fieldObject != null) { map.put(fieldMap.getName(), fieldObject); } fieldMap.setAccessible(fieldMapAccessible); } } field.setAccessible(fieldAccessible); } catch (Exception e) { e.printStackTrace(); } } defaultSerializer.serialize(src, gen, provider); } @Override public Class<Object> handledType() { return Object.class; } }
which goes through all fields, when I find one that extends from a Map I go through all the fields of this one and add it to the Map ignoring the object’s fields, so the Serializer works perfectly.
EDIT: to Deserializer properly I do this:
import java.io.IOException; import java.lang.reflect.Field; import java.util.Map; import org.springframework.util.ReflectionUtils; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; @SuppressWarnings("rawtypes") public class MyClassDeserializer extends JsonDeserializer implements ResolvableDeserializer { private JsonDeserializer defaultDeserializer; protected MyClassDeserializer(JsonDeserializer deserializer) { this.defaultDeserializer = deserializer; } @SuppressWarnings("unchecked") @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { Object obj = defaultDeserializer.deserialize(p, ctxt); Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { try { boolean fieldAccessible = field.isAccessible(); field.setAccessible(true); Object object = ReflectionUtils.getField(field, obj); if (object != null && object instanceof Map) { Field[] fieldsMap = object.getClass().getDeclaredFields(); Map map = (Map) object; for (Object key : map.keySet()) { for (Field fieldMap : fieldsMap) { if (fieldMap.getName().equals((String) key)) { if (fieldMap.getName().equalsIgnoreCase("serialVersionUID")) { continue; } boolean fieldMapAccessible = fieldMap.isAccessible(); fieldMap.setAccessible(true); Object fieldObject = ReflectionUtils.getField(fieldMap, object); if (fieldObject == null) { fieldMap.set(object, map.get(key)); map.replace(key, null); } fieldMap.setAccessible(fieldMapAccessible); } } } Object[] keys = map.keySet().toArray(); for (int i = 0; i < keys.length; i++) { if(map.get(keys[i])==null) { map.remove(keys[i]); } } } field.setAccessible(fieldAccessible); } catch (Exception e) { e.printStackTrace(); } } return obj; } @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); } }