FasterXml — JsonSerializer HashMap

Я использую jackson-databind версии 2.12.3 для сериализации возврата объекта, который должен возвращаться следующим образом:

{
  "field1":"value1",
  "field2":"value2",
  "links":{
    "field":{
      "href":"/link"
    },
    "test":{
      "href":"/test"
    }
  }
}

Мои классы таковы:

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;
  ...
}

Возврат 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);

Однако с ObjectMapper по умолчанию link.setField игнорируется, а возвращаемый json:

{
  "field1":"value1",
  "field2":"value2",
  "links":{
    "test":{
      "href":"/test"
    }
  }
}

Я попытался провести некоторые тесты с JsonSerializer, но не смог сделать что-то общее для всех классов, которые расширяют HashMap (эти классы генерируются из PSD2 YAML от BerlinGroup, поэтому я бы не хотел менять сгенерированный класс).

Есть ли общий способ сделать это, или мне следует создать класс сериализации для каждого класса, который расширяет HashMap?


person Samuel Facchinello    schedule 07.06.2021    source источник
comment
У вас есть геттер для свойства поля в классе Link (›геттер делает частное поле сериализуемым/десериализуемым)?   -  person TacheDeChoco    schedule 07.06.2021
comment
@TacheDeChoco да: @JsonProperty(field) public HrefType getField() { return field; }   -  person Samuel Facchinello    schedule 07.06.2021


Ответы (2)


Сочинение

Прежде всего, я предлагаю вам использовать композицию вместо наследования в данном конкретном случае. Ваш код будет выглядеть следующим образом:

    private class Link {
        private final HrefType field;
        private final HashMap<String, HrefType> test;

        public Link(HrefType field) {
            this.field = field;
        }

        public HrefType getField() {
            return field;
        }

        public HashMap<String, HrefType> getTest() {
            return test;
        }
    }

И сериализация будет работать нормально, как и ожидалось.

Сериализатор

Но в случае, если вы не можете изменить исходный код, вы можете написать свой собственный StdSerializer. Например:

    private class LinkSerializer extends StdSerializer<Link> {

        public LinkSerializer() {
            super(Link.class);
        }

        @Override
        public void serialize(Link link, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeStartObject();
            final HrefType field = link.getField();
            jsonGenerator.writeObjectField("field", field);
            jsonGenerator.writeObjectField("test", new HashMap<>(link));
            jsonGenerator.writeEndObject();
        }
    }

И объявите его над своим классом Link:

    @JsonSerialize(using = LinkSerializer.class)
    private static class Link extends HashMap<String, HrefType> {
        private final HrefType field;

        public Link(HrefType field) {
            this.field = field;
        }

        public HrefType getField() {
            return field;
        }
    }
person Volodya Lombrozo    schedule 07.06.2021
comment
В обоих случаях это работает, но поскольку они генерируются классами из YAML, я бы не хотел менять их каждый раз, когда они генерируются в каком-либо обновлении. Есть ли другой способ сделать что-то общее для всех классов, расширяющих хэш-карту? - person Samuel Facchinello; 08.06.2021
comment
github. com/eugenp/tutorials/tree/master/jackson-modules/jackson/ Вот несколько примеров реализации наследования в Джексоне. Может быть, вы найдете то, что вы хотите. Но, честно говоря, я не нашел того, что могло бы решить вашу проблему простым способом. - person Volodya Lombrozo; 08.06.2021
comment
Я попробую что-нибудь, ткс много. - person Samuel Facchinello; 08.06.2021

на основе этот ответ я разработал этот общий метод создания для всех объектов, которые расширяют карту:

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

который проходит через все поля, когда я нахожу тот, который выходит из карты, я просматриваю все поля этого и добавляю его в карту, игнорируя поля объекта, поэтому сериализатор работает отлично.

РЕДАКТИРОВАТЬ: для десериализации правильно я делаю это:

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

}
person Samuel Facchinello    schedule 09.06.2021