如何在 Jackson 中将原始 json 字符串注入字段?

How to express in Jackson injection of original json string into a field?

有一个JSON字符串""" {"a": 1, "b": "hello"} """

我想创建一个模型,包含“a”、“b”和“originalJson”。

class MyModel{

    public int a;
    public String b;
    public String originalJson;
} 

void test1(){
    var payload = """ {"a": 1, "b": "hello"} """;

    // how to apply Jackson here?
    MyModel model = magicParse(payload, MyModel.class);

    assertEquals(1, model.a);
    assertEquals("hello", model.b);
    assertEquals(payload, model.originalJson);
}

如果我们将它扩展到更广泛的应用

void test2(){
    var payload = """ [
          {"a": 1, "b": "hello"},
          {"a": 2, "b": "bye"}
    ]
    """;

    // how to apply Jackson here?
    MyModel[] models = magicParse(payload, MyModel[].class);

    var firstModel = model[0]
    assertEquals(1, firstModel.a);
    assertEquals("hello", firstModel.b);
    assertEquals("""{"a": 1, "b": "hello"}""", firstModel.originalJson);


    var secondModel = model[2]
    assertEquals(2, secondModel.a);
    assertEquals("bye", secondModel.b);
    assertEquals("""{"a": 2, "b": "bye"}""", secondModel.originalJson);
}

在 Jackson 中是否有一种自然的方式来做到这一点(注释、配置……)?

您可以使用 ObjectMapper.readTree():

private static final ObjectMapper MAPPER = new ObjectMapper();

public MyModel magicParse(String payload) 
        throws JsonMappingException, JsonProcessingException {
    JsonNode root = MAPPER.readTree(payload);
    int a = root.get("a").asInt();
    String b = root.get("b").asText();
    return new MyModel(a, b, payload);
}

您还需要 MyModel 的构造函数(或 setter,具体取决于您的情况):

public class MyModel {

    public int a;
    public String b;
    public String originalJson;

    MyModel(int a, String b, String originalJson) {
        this.a = a;
        this.b = b;
        this.originalJson = originalJson;
    }

}

尝试了几种使用自定义反序列化器的解决方案,但看起来它们需要大量代码来模糊主要意图。我看到的基本问题是,在反序列化器中我们可以访问 JsonParser,它是有状态的并保持“一次通过”算法,因此不可能从中获取 JSON subtree/substring它不会干扰正常的解析过程。

另一种方法更具可读性,尽管当我们将有效负载反序列化两次时可能会对性能产生影响。 这个想法是获取 JsonNode 并使用它反序列化模型并进行进一步的转换:在基本反序列化之后和返回结果之前传递根节点。

// utility for mapping
public static <T, R> R readValue(
    String json,
    Class<T> clazz,
    BiFunction<T, JsonNode, R> transformValue
){
    JsonNode node = getObjectMapper().readValue(json, JsonNode.class);
    T value = mapper.treeToValue(node, clazz);
    return transformValue.apply(value, node);
}


// concrete usage
MyModel getModel(String payload){
    return readValue(
      payload, 
      MyModel.class, 
      (m, node) -> {
          m.payload = node.toString(); 
          return m;
    );
}