Можно ли настроить пользовательские десериализаторы Jackson на уровне класса для разных типов данных? - PullRequest
2 голосов
/ 01 апреля 2019

Мне нужно десериализовать длинный и сложный json, для которого я написал набор java-классов для отображения данных, и мне пришлось написать собственные десериализаторы для многих полей различных типов (включая String, Boolean, BigDecimal, и др.) .

Я знаю, что могу аннотировать все поля в классах Java с помощью соответствующего пользовательского десериализатора (как показано ниже), но тогда мне нужно будет аннотировать почти все поля во всех классах.

@JsonDeserialize(using = CustomBooleanJsonDeserializer.class)
private boolean active;

Я также знаю, что могу зарегистрировать модуль в Spring по умолчанию ObjectMapper (например, здесь ), но я просто хочу использовать эти пользовательские десериализаторы для этих конкретных классов.

@Bean
public Module customDeserializersModule() {
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Boolean.class, new CustomBooleanJsonDeserializer());
    // add other custom deserializers 
    return module;
}

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

@RequestMapping(method = RequestMethod.POST, value = "/data")
public ResponseEntity<ServerInfo> register(@RequestBody DataMapper data) {
   // DataMapper is the target POJO class of the json's deserialization
}

Короче, я ищу что-то подобное на уровне класса:

@JsonDeserialize(using = CustomStringJsonDeserializer.class, forType = String.class)
@JsonDeserialize(using = CustomBooleanJsonDeserializer.class, forType = Boolean.class)
@JsonDeserialize(using = CustomBigDecimalJsonDeserializer.class, forType = BigDecimal.class)
public class DataMapper implements Serializable {
    // obviously, @JsonDeserialize doesn't have a forType method
}

или, может быть, каким-то образом реализовать специальный десериализатор для класса DataMapper, который определяет, как десериализовать каждое поле в соответствии с его типом данных (без необходимости аннотировать каждое поле):

@JsonDeserialize(using = DataMapperJsonDeserializer.class)
public class DataMapper implements Serializable {
    // How can I implement the DataMapperJsonDeserializer with these 
    // characteristics? I know about the ContextualDeserializer interface, 
    // but I don't know how to use it without annotating each field.
}

или каким-то образом ограничение действия модуля только одним пакетом или набором классов :

module.restrictedTo(/*some package or set of classes*/);
// com.fasterxml.jackson.databind.Module doesn't have a restrictedTo method

Ответы [ 2 ]

2 голосов
/ 02 апреля 2019

Вы можете попробовать использовать SimpleModule вместе с ContextualDeserializer интерфейсом.Первый может использоваться для переноса десериализатора по умолчанию, а второй - для проверки конфигурации типа - проверки аннотации.

Давайте начнем с аннотации:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ForceCustomDeserializer {
}

Я предполагаю, что у вас есть только одна пользовательская реализация для данного типано в случае, если это не так, пройдите выше аннотации и предоставьте некоторую дополнительную информацию, позволяющую использовать правильные десериализаторы.Например, ниже мы видим два пользовательских десериализатора, которые дополнительно регистрируют некоторую информацию и запускают десериализацию по умолчанию.Базовый десериализатор используется, потому что в случае, если у вас есть какая-то дополнительная конфигурация, мы не теряем ее.

class CustomBoolDeserializer extends StdScalarDeserializer<Boolean> implements ContextualDeserializer {

    private NumberDeserializers.BooleanDeserializer base;

    public CustomBoolDeserializer(NumberDeserializers.BooleanDeserializer base) {
        super(Boolean.class);
        this.base = base;
    }

    @Override
    public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        System.out.println("Custom BooleanDeserializer ....");

        return base.deserialize(p, ctxt);
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        Class<?> parent = property.getMember().getDeclaringClass();
        ForceCustomDeserializer annotation = parent.getAnnotation(ForceCustomDeserializer.class);

        return annotation == null ? base : this;
    }
}

class CustomStringDeserializer extends StringDeserializer implements ContextualDeserializer {

    private final StringDeserializer base;

    public CustomStringDeserializer(StringDeserializer base) {
        this.base = base;
    }

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        System.out.println("Custom StringDeserializer ....");

        return base.deserialize(p, ctxt);
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        Class<?> parent = property.getMember().getDeclaringClass();
        ForceCustomDeserializer annotation = parent.getAnnotation(ForceCustomDeserializer.class);

        return annotation == null ? base : this;
    }
}

Мы можем протестировать вышеописанные пользовательские реализации, как показано ниже:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        SimpleModule forcedCustomModule = new SimpleModule();
        forcedCustomModule.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                if (deserializer instanceof StringDeserializer) {
                    // wrap with yours or return new deserializer
                    return new CustomStringDeserializer((StringDeserializer) deserializer);
                }
                if (deserializer instanceof NumberDeserializers.BooleanDeserializer) {
                    // wrap with yours or return new deserializer
                    return new CustomBoolDeserializer((NumberDeserializers.BooleanDeserializer) deserializer);
                }
                // override for other types

                return deserializer;
            }
        });

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

        System.out.println(mapper.readValue(jsonFile, Pojo.class));
    }
}

@ForceCustomDeserializer
class Pojo {

    private String name;
    private boolean bool;

    // getters, setters, toString
}

Выше приведенный ниже пример JSON полезная нагрузка:

{
  "name": "Jackson",
  "bool": true
}

отпечатки:

Custom StringDeserializer ....
Custom BooleanDeserializer ....
Pojo{name='Jackson', bool=true}

См. Также:

0 голосов
/ 04 апреля 2019

Вы можете определить собственный десериализатор для класса ( как вторая идея в вопросе ) и использовать свой собственный ObjectMapper внутри:

public class DataMapperJsonDeserializer extends JsonDeserializer<DataMapper> {

    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");

    static {
        SimpleModule module = new SimpleModule();
        module.addDeserializer(BigInteger.class, new CustomBigIntegerJsonDeserializer());
        module.addDeserializer(BigDecimal.class, new CustomBigDecimalJsonDeserializer());
        module.addDeserializer(Boolean.class, new CustomBooleanJsonDeserializer());
        module.addDeserializer(String.class, new CustomStringJsonDeserializer());
        objectMapper.registerModule(module);
        objectMapper.addMixIn(DataMapper.class, DefaultJsonDeserializer.class);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDateFormat(simpleDateFormat);
    }

    @Override
    public DataMapper deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return objectMapper.readValue(jsonParser, DataMapper.class);
    }

    @JsonDeserialize
    private interface DefaultJsonDeserializer {
        // Reset default json deserializer
    }

}

Обратите внимание на использование Смешанных аннотаций Джексона ( DefaultJsonDeserializer interface ) для динамического удаления пользовательского десериализатора из класса POJO, избегая StackOverflowError, который в противном случае был бы брошен в результате objectMapper.readValue(jsonParser, DataMapper.class).


Тогда просто аннотируем класс POJO:

@JsonDeserialize(using = DataMapperJsonDeserializer.class)
public class DataMapper implements Serializable {
    // It is not necessary to annotate each field with custom deserializers.
}

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

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