【问题标题】:Include POJO's raw JSON as an attribute of the POJO包含 POJO 的原始 JSON 作为 POJO 的属性
【发布时间】:2021-04-17 16:27:24
【问题描述】:

这可能吗?我想将 JSON 反序列化为 POJO 结构,但除此之外,还要在 POJO(或子 POJO)中保存原始 json 的副本。例如,假设我有这样的结构:

{
  "test": 123,
  "testStr": "foo",
  "testSubModel": {
    "testStr2": "foobar",
    "testFloat2": 1.2
  }
}

现在我有一组简单的两个 POJO:

package test.model;

public class TestModel {

    private int test;
    private String testStr;
    private TestSubModel testSubModel;

    public int getTest() {
        return test;
    }

    public String getTestStr() {
        return testStr;
    }

    public TestSubModel getTestSubModel() {
        return testSubModel;
    }
}

package test.model;

public class TestSubModel {

    private String testStr2;
    private float testFloat2;
    private String rawJson; // i want this to contain something like { "testStr2": "foobar",  "testFloat2": 1.2 }
    
    public String getTestStr2() {
        return testStr2;
    }

    public float getTestFloat2() {
        return testFloat2;
    }
}

除了正确设置 pojo 字段之外,是否可以使用类的完整 JSON 设置 TestSubModel 中的 rawJson

虽然我可以使用自定义方法重新发明它,但我没有映射的任何额外 JSON 字段都会丢失,我想保留这些字段以用于异常记录目的(即,我需要上游系统发送的原始 JSON 和不是重新构建的,可能会丢失我通常不存储在 POJO 中的字段)。

我希望有一种方法可以使用注释(但不认为它在那里)或自定义后反序列化器挂钩(这样杰克逊可以按照通常的方式来映射对象,而无需我编写这个代码都是我自己为每个适用的类)。我用DelegatingDeserializer 尝试了一些东西,但JsonParser 不可重复,就像我读到它时,除了取出树并转换为字符串之外,它不能重复使用调用Object deserializedObject = super.deserialize(p, ctxt);

【问题讨论】:

    标签: java jackson


    【解决方案1】:

    我决定尝试一下,尽管结果比我预期的要复杂。使用此解决方案,只需使用您的ObjectMapper 注册rawJsonModule,然后将@RawJson 应用于目标字段。

    代码肯定也可以稍微优化一下(反序列化器中的反射肯定不是最理想的)。如果您有任何问题,请告诉我。

    输出:TestModel{test=123, testStr='foo', testSubModel=TestSubModel{testStr2='foobar', testFloat2=1.2, rawJson='{"testStr2":"foobar","testFloat2":1.2}'}}

    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.*;
    import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
    import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
    import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    
    import java.io.IOException;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    
    public class SO67140419 {
    
        public static void main(String[] args) throws JsonProcessingException {
            String json = """
            {
              "test": 123,
              "testStr": "foo",
              "testSubModel": {
                "testStr2": "foobar",
                "testFloat2": 1.2
              }
            }""";
    
            var rawJsonModule = new SimpleModule();
            // Credits to schummar for this technique; take the default deserializer and 
            // pass it to RawJsonDeserializer, which intercepts all deserialization
            // https://stackoverflow.com/a/18405958/5378187
            rawJsonModule.setDeserializerModifier(new BeanDeserializerModifier() {
                @Override
                public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                    BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                    return new RawJsonDeserializer(beanDesc, deserializer);
                }
            });
    
            var mapper = new ObjectMapper();
            mapper.registerModule(rawJsonModule);
    
            var model = mapper.readValue(json, TestModel.class);
            System.out.println(model);
        }
    
    }
    
    @JsonIgnore
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @interface RawJson {}
    
    class RawJsonDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer {
    
        /**
         * The default deserializer
         */
        private final JsonDeserializer<?> deser;
        private final BeanDescription desc;
    
        public RawJsonDeserializer(BeanDescription desc, JsonDeserializer<?> deser) {
            super((JavaType) null);
            this.deser = deser;
            this.desc = desc;
        }
    
        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            // Read p into a json node in case we need it later for an @RawJson field
            JsonNode node = p.getCodec().readTree(p); 
            p = p.getCodec().treeAsTokens(node); // Refresh p
    
            if (p.getCurrentToken() == null) {
                p.nextToken();
            }
    
            // Deserialize object using the default deserialization
            Object obj = deser.deserialize(p, ctxt);
    
            // Check for RawJson annotated fields
            for (Field f : desc.getBeanClass().getDeclaredFields()) {
    
                if (f.getDeclaredAnnotation(RawJson.class) == null) {
                    continue;
                } else if (f.getType() != String.class) {
                    throw new IllegalStateException("@RawJson annotation applied to non-string field: " + f);
                }
    
                // Set the field to the json we stored earlier
                try {
                    f.setAccessible(true);
                    f.set(obj, node.toString());
                } catch (IllegalAccessException e) {
                    throw new IOException(e);
                }
            }
    
            return obj;
        }
    
        @Override
        public void resolve(DeserializationContext ctxt) throws JsonMappingException {
            // Not sure why we need this but ok
            if (deser instanceof ResolvableDeserializer rd) {
                rd.resolve(ctxt);
            }
        }
    }
    
    
    class TestModel {
    
        private int test;
        private String testStr;
        private TestSubModel testSubModel;
    
        public int getTest() {
            return test;
        }
    
        public String getTestStr() {
            return testStr;
        }
    
        public TestSubModel getTestSubModel() {
            return testSubModel;
        }
    
        @Override
        public String toString() {
            return "TestModel{" +
                "test=" + test +
                ", testStr='" + testStr + '\'' +
                ", testSubModel=" + testSubModel +
                '}';
        }
    }
    
    class TestSubModel {
    
        private String testStr2;
        private float testFloat2;
        @RawJson
        private String rawJson; // i want this to contain something like { "testStr2": "foobar",  "testFloat2": 1.2 }
    
        public String getTestStr2() {
            return testStr2;
        }
    
        public float getTestFloat2() {
            return testFloat2;
        }
    
        @Override
        public String toString() {
            return "TestSubModel{" +
                "testStr2='" + testStr2 + '\'' +
                ", testFloat2=" + testFloat2 +
                ", rawJson='" + rawJson + '\'' +
                '}';
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-15
      • 1970-01-01
      • 1970-01-01
      • 2021-09-03
      • 1970-01-01
      • 2019-10-08
      相关资源
      最近更新 更多