简体   繁体   English

Jackson,基于另一个属性(依赖属性)反序列化属性

[英]Jackson, deserialize property based on another property (dependent property)

Using Jackson, is there a way to deserialize a proprty that depends on the value of another property?使用 Jackson,有没有办法反序列化依赖于另一个属性值的属性?

if i have this json {"foo":"a","bar":"b"} i'd like to deserialize it to the Test class below as Test [foo=a, bar=b_a] , where bar is the value of the json property "bar" and the value of the property "foo".如果我有这个 json {"foo":"a","bar":"b"}我想将它反序列化为下面的测试 class 作为Test [foo=a, bar=b_a] ,其中 bar 是json 属性“bar”的值和属性“foo”的值。

Of course this is a trivial example, the real deal would be to deserialize a datamodel entity: {"line":"C12", "machine": {"line":"C12", "code":"A"}} machine.line and line are always the same, and i'd like to express it like this: {"line":"C12", "machine": "A"}当然这是一个微不足道的例子,真正的交易是反序列化数据模型实体: {"line":"C12", "machine": {"line":"C12", "code":"A"}} machine.line 和 line 总是一样的,我想这样表达: {"line":"C12", "machine": "A"}

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

public abstract class Main{

    private static class Test {
        @JsonProperty
        private String foo;
        @JsonProperty
        @JsonDeserialize(using = CustomDeserializer.class)
        private String bar;

        // ...other fields to be deserialized with default behaviour

        private Test() {
        }

        public Test(String a, String bar) {
            this.foo = a;
            this.bar = bar;
        }

        @Override
        public String toString() {
            return "Test [foo=" + foo + ", bar=" + bar + "]";
        }
    }

    private static class CustomDeserializer extends StdDeserializer<String> {

        protected CustomDeserializer() {
            super(String.class);
        }

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            String foo = //how to get foo property?
            String value = p.getValueAsString();
            if (!foo.isEmpty()) {
                return value + "_" + foo;
            } else {
                return value;
            }
        }

    }

    public static void main(String[] args) throws IOException {

        ObjectMapper mapper = new ObjectMapper();

        Test foo2 = mapper.readValue("{\"foo\":\"a\",\"bar\":\"b\"}", Test.class);
        System.out.println(foo2); // Test [foo=a, bar=b_a]

    }

}

One way to solve your problem is specify a custom deserializer that involves your Test class instead of your string field because the deserialization of your property is based on the value of another property:解决问题的一种方法是指定一个自定义反序列化器,它涉及您的Test class 而不是您的字符串字段,因为您的属性的反序列化是基于另一个属性的值:

public class CustomDeserializer extends JsonDeserializer<Test> {}

@JsonDeserialize(using = CustomDeserializer.class)
public class Test {}

Then you can deserialize your object reading the JsonNode tree built from your input string:然后你可以反序列化你的 object 读取从你的输入字符串构建的JsonNode树:

public class CustomDeserializer extends JsonDeserializer<Test> {

    @Override
    public Test deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        String foo = node.get("foo").asText();
        String bar = node.get("bar").asText();
        if (!foo.isEmpty()) {
            bar = (bar + '_' + foo);
        }
        return new Test(foo, bar);
    }
    
}

//your example
public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Test foo2 = mapper.readValue("{\"foo\":\"a\",\"bar\":\"b\"}", Test.class);
        System.out.println(foo2); // Test [foo=a, bar=b_a]
    }
}

I got a similar problem today and I wanted to share my solution.我今天遇到了类似的问题,我想分享我的解决方案。 So instead of using a @JsonDeserialize , I use a @JsonCreator on the parent object with a package private constructor to accept the "raw" properties and then I can process this data and return better objects.因此,我没有使用@JsonDeserialize ,而是在父级@JsonCreator上使用 @JsonCreator 和 package 私有构造函数来接受“原始”属性,然后我可以处理这些数据并返回更好的对象。

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

class Scratch {
    public static void main(String[] args) throws Exception{
        final var testData = "{\"foo\":\"a\",\"bar\":\"b\"}";
        final var mapper = new ObjectMapper();
        final var testObj = mapper.readValue(testData, Test.class);

        System.out.println(testObj); // Test[foo=a, bar=a_b]
    }

    record Test (
            String foo,
            String bar
    ){
        @JsonCreator Test(
                @JsonProperty("foo") String foo,
                @JsonProperty("bar") String bar,
                @JsonProperty("_dummy") String _dummy // extra param for the constructor overloading
        ) {
            this(foo, deserializeBar(foo, bar));
        }

        private static String deserializeBar(String foo, String bar) {
            if (foo == null || foo.isEmpty()) {
                return bar;
            }

            return "%s_%s".formatted(foo, bar);
        }
    }
}

In the end, I've resorted using BeanDeserializerModifier最后,我使用BeanDeserializerModifier

Please notice that the following code is not fully functioning because it relies on code I'm not allowed to share, but it should suffice to get the idea.请注意,以下代码未完全运行,因为它依赖于我不允许共享的代码,但它应该足以理解这个想法。

package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;

public class JsonDelegateDeserializerModule extends SimpleModule {
        
    // !! must be registered as guice factory
    public interface JsonDelegateDeserializerFactory {
        JsonDelegateDeserializerModule create(String packagePath);
    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface JsonDelegateDeserializer {

        public Class<? extends StdDeserializer<?>> deserializer();

        public Class<?> forType();
    }

    protected interface JsonDeserializerFactory {
        // non metto nessun generic in TagHandler o guice non riesce piu a creare la
        // factory!
        @SuppressWarnings("rawtypes")
        public JsonDeserializer create(JsonDeserializer baseDeserializer);
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonDelegateDeserializerModule.class);

    @Inject
    private FactoryInjector injector;

    private final String packagePath;

    @AssistedInject
    protected JsonDelegateDeserializerModule(@Assisted String packagePath) {
        super();
        this.packagePath = packagePath;
    }

    @Override
    public String getModuleName() {
        return JsonDelegateDeserializerModule.class.getSimpleName() + "[" + packagePath + "]";
    }

    @Override
    public Object getTypeId() {
        return JsonDelegateDeserializerModule.class.getSimpleName() + "[" + packagePath + "]";
    }

    @Override
    public void setupModule(SetupContext context) {

        Reflections reflectios = new Reflections(packagePath, new SubTypesScanner(), new TypeAnnotationsScanner());

        Map<Class<?>, JsonDeserializerFactory> classToDeserializerFactory = new HashMap<>();

        Set<Class<?>> classesWithModifier = reflectios.getTypesAnnotatedWith(JsonDelegateDeserializer.class);
        for (Class<?> classWithModifier : classesWithModifier) {
            JsonDelegateDeserializer annotation = classWithModifier.getAnnotation(JsonDelegateDeserializer.class);
            if (annotation != null) {
                Class<? extends StdDeserializer<?>> deserializerType = annotation.deserializer();
                Class<?> forType = annotation.forType();
                try {
                    JsonDeserializerFactory factory = injector.getFactory(JsonDeserializerFactory.class,
                            deserializerType);
                    classToDeserializerFactory.put(forType, factory);
                } catch (Exception e) {
                    LOGGER.error("Exception was thown while creating deserializer {} for type {}:", deserializerType,
                            forType, e);
                    throw new RuntimeException(e);
                }
            }
        }

        if (!classToDeserializerFactory.isEmpty()) {
            setDeserializerModifier(new BeanDeserializerModifier() {
                @Override
                public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
                        JsonDeserializer<?> deserializer) {
                    List<Class<?>> possibleTypesList = new LinkedList<>();

                    if (deserializer instanceof BeanDeserializer) {
                        for (Entry<Class<?>, JsonDeserializerFactory> entry : classToDeserializerFactory.entrySet()) {
                            Class<?> type = entry.getKey();
                            if (type.isAssignableFrom(deserializer.handledType())) {
                                possibleTypesList.add(type);
                            }
                        }

                        if (possibleTypesList.size() > 1) {
                            possibleTypesList.sort(new Comparator<Class<?>>() {
                                @Override
                                public int compare(Class<?> o1, Class<?> o2) {
                                    if (o1.isAssignableFrom(o2)) {
                                        return 1;
                                    } else {
                                        return -1;
                                    }
                                }
                            });
                        }
                        
                        Class<?> type = Utils.first(possibleTypesList);
                        if (type == null) {
                            return super.modifyDeserializer(config, beanDesc, deserializer);
                        } else {
                            JsonDeserializerFactory factory = classToDeserializerFactory.get(type);
                            JsonDeserializer<?> modifiedDeserializer = factory.create(deserializer);
                            return super.modifyDeserializer(config, beanDesc, modifiedDeserializer);
                        }
                    } else {
                        // è gia stato impostato un deserializzatore piu specifico, non imposato questo
                        return super.modifyDeserializer(config, beanDesc, deserializer);
                    }
                }
            });
        }

        super.setupModule(context);
    }

}

then you can simply annotate the Mixin to add the custom deserializer然后您可以简单地注释 Mixin 以添加自定义解串器

@JsonDelegateDeserializer(deserializer = LoadLineDeserializer.class, forType = Line.class)
public interface LineMixIn {

    public static class LoadLineDeserializer extends DelegatingDeserializer {
        @AssistedInject
        public LoadLineDeserializer(@Assisted JsonDeserializer baseDeserializer, LineService lineService) {
            super(baseDeserializer);
        }

        // ...
    }
    
    // ...

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM