如何在 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;
);
}
有一个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;
);
}