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?
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);
}
}