当前位置: 首页 > 知识库问答 >
问题:

将POJO的原始JSON作为POJO的一个属性

常枫涟
2023-03-14

这可能吗?我想将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中的字段的res构造的JSON)。

我希望有一种方法可以通过注释(但不要认为它在那里)或自定义后反序列化钩子(这样Jackson就可以做它通常的事情来映射对象,而不需要我自己为每个适用的类编写代码)。我尝试了一些与委托解析器,但JsonParser是不可重复的,因为当我读到它一次不能重用调用对象反序列化对象=super.deserialize(p, ctxt);除了得到树和转换为字符串。

共有1个答案

何星鹏
2023-03-14

我决定尝试一下,尽管结果比我想象的要复杂。使用此解决方案,只需将rawJsonMoules注册为ObjectMapper,然后将@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 + '\'' +
            '}';
    }
}
 类似资料:
  • 问题内容: 我需要从Java pojo创建这样的Json有效负载: 所以我创建了一个Java pojo像这样: 我喜欢Label类中的“ add”属性是动态的,因此它也可以采用“ remove”,“ set”之类的值。除了为每个人创建另一个POJO之外,还有其他方法吗? 问题答案: 您可以使用JsonAnyGetter批注来创建动态对。下面的示例显示了如何为同一类生成3个不同的键: 上面的代码打印

  • 问题内容: 我正在将Jersey用于REST WS,并且得到的响应为JSON。 我想将此响应转换为POJO。怎么做 ? 问题答案: 要在Java和JSON之间进行转换,有很多可供选择的API 。 您可以“手动”遍历JSON组件并提取值以填充Java对象,或者可以使用JSON到Java的绑定API来解决许多低级映射问题。

  • 我有下面的JSON,我想解析到下面的bean类。 豆类 1 Bean类2 在这里,我无法使用下面的代码解析这些JavaPOJO。 jsonObjMapper.readValue(jsonString,DistributedCookieBean.class); 堆栈跟踪: org . code Haus . Jackson . map . jsonmappingexception:无法从START_

  • 假设我有一个具有公共字段 和 的类 。假设我有另一个 pojo 类 ,但它使用 setter 和 getter,所以它有 setX() 和 setY()。 我想使用一些自动方法从实例复制到并返回。 至少使用默认设置,推土机的 未正确复制字段。 那么,是否有一个简单的配置更改可以让我使用Dozer或其他库来完成上述操作?

  • 我正在尝试将JSON转换为POJO类。这个JSON是我从第三方REST API调用得到的,我想把它转换成POJO类。为此,我使用jackson databind jar,下面是我代码的一部分。 这里现在不是POJO类,我声明了Object类型的ModelObjcet变量,我的问题是我们是否需要在将JSON转换为POJO之前创建带有必填字段和getter setter方法的POJO类? 如果是,那么

  • 有人能帮助我,我如何反序列化下面的JSON,我不能改变?我正在使用Jackson进行序列化。 列可以具有未知数量的标题及其值,例如,“Header1”用于行数组中。 到目前为止,我有以下结构: 问题是当我反序列化成QueryResult时映射是空的;我了解TypeReference,但不知道如何指定TypeReference 编辑: 我的对象映射器代码如下: queryResultString的内