Пользовательские сериализация и десериализация Джексона - PullRequest
2 голосов
/ 19 марта 2019

Я не могу найти правильный способ реализации пользовательской сериализации / десериализации с Джексоном.У меня есть много классов (~ 50) с примитивными полями, которые следует сериализовать / десериализовать не как примитивы.например:

class User {
    int height // this field should be serialized as "height": "10 m"
}

class Food {
    int temperature // this field should be serialized as "temperature": "50 C"
}

class House {
    int width // this field should be serialized as "width": "10 m"
}

все сериализации и десериализации очень похожи, мне просто нужно добавить суффикс после целого числа (C, страниц, метров и т. д.)

Простой способсделать это, чтобы поместить пару аннотаций @JsonSerialize / @JsonDeserialize к каждому такому полю и реализовать их.Но в итоге я получу 100 очень похожих сериализаторов / десериализаторов.

Я думал о добавлении пользовательских аннотаций к каждому полю, скажем @Units("Degree") или @Units("Meters"), к таким целочисленным полям и реализацииSerializationProvider, который создаст сериализаторы общим способом на основе значения аннотации.Но я не нашел места, где можно найти информацию об аннотациях собственности.

1 Ответ

3 голосов
/ 20 марта 2019

Идея с Unit аннотацией действительно хороша. Нам нужно только добавить пользовательские реализации com.fasterxml.jackson.databind.ser.BeanSerializerModifier и com.fasterxml.jackson.databind.ser.BeanPropertyWriter. Давайте сначала создадим наш класс аннотаций:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Unit {
    String value();
}

POJO модель может выглядеть следующим образом:

class Pojo {

    private User user = new User();
    private Food food = new Food();
    private House house = new House();

    // getters, setters, toString
}

class User {

    @Unit("m")
    private int height = 10;

    // getters, setters, toString
}

class Food {

    @Unit("C")
    private int temperature = 50;

    // getters, setters, toString
}

class House {

    @Unit("m")
    private int width = 10;

    // getters, setters, toString
}

Имея все это нам нужно настроить сериализацию свойства:

class UnitBeanSerializerModifier extends BeanSerializerModifier {

    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
        for (int i = 0; i < beanProperties.size(); ++i) {
            final BeanPropertyWriter writer = beanProperties.get(i);
            AnnotatedMember member = writer.getMember();
            Unit units = member.getAnnotation(Unit.class);
            if (units != null) {
                beanProperties.set(i, new UnitBeanPropertyWriter(writer, units.value()));
            }
        }
        return beanProperties;
    }
}

class UnitBeanPropertyWriter extends BeanPropertyWriter {

    private final String unit;

    protected UnitBeanPropertyWriter(BeanPropertyWriter base, String unit) {
        super(base);
        this.unit = unit;
    }

    @Override
    public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
        gen.writeFieldName(_name);
        final Object value = (_accessorMethod == null) ? _field.get(bean) : _accessorMethod.invoke(bean, (Object[]) null);
        gen.writeString(value + " " + unit);
    }
}

Используя SimpleModule, мы можем зарегистрировать его и использовать с ObjectMapper:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        SimpleModule unitModule = new SimpleModule();
        unitModule.setSerializerModifier(new UnitBeanSerializerModifier());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(unitModule);

        Pojo pojo = new Pojo();
        System.out.println(mapper.writeValueAsString(pojo));
    }
}

печать:

{
  "user" : {
    "height" : "10 m"
  },
  "food" : {
    "temperature" : "50 C"
  },
  "house" : {
    "width" : "10 m"
  }
}

Конечно, вам нужно протестировать его и обработать все угловые случаи, но приведенный выше пример показывает общую идею. Аналогичным образом мы можем справиться с десериализацией. Нам нужно реализовать пользовательский BeanDeserializerModifier и один пользовательский UnitDeserialiser:

class UnitBeanDeserializerModifier extends BeanDeserializerModifier {

    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        JsonDeserializer<?> jsonDeserializer = super.modifyDeserializer(config, beanDesc, deserializer);
        if (jsonDeserializer instanceof StdScalarDeserializer) {
            StdScalarDeserializer scalarDeserializer = (StdScalarDeserializer) jsonDeserializer;
            Class scalarClass = scalarDeserializer.handledType();
            if (int.class == scalarClass) {
                return new UnitIntStdScalarDeserializer(scalarDeserializer);
            }
        }
        return jsonDeserializer;
    }
}

и пример десериализатора для int:

class UnitIntStdScalarDeserializer extends StdScalarDeserializer<Integer> {

    private StdScalarDeserializer<Integer> src;

    public UnitIntStdScalarDeserializer(StdScalarDeserializer<Integer> src) {
        super(src);
        this.src = src;
    }

    @Override
    public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String value = p.getValueAsString();
        String[] parts = value.split("\\s+");
        if (parts.length == 2) {
            return Integer.valueOf(parts[0]);
        }
        return src.deserialize(p, ctxt);
    }
}

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

unitModule.setDeserializerModifier(new UnitBeanDeserializerModifier());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...