Jackson @JsonAnySetter 在与 Jackson ObjectMapper treeToValue 方法一起使用时忽略重复键的值
Jackson @JsonAnySetter ignores values of duplicate key when used with Jackson ObjectMapper treeToValue method
我正在使用 Jackson 库反序列化 JSON。在 JSON 中,我有一些自定义字段,其值可以是任何值,因此我尝试使用 @JsonAnySetter
和 @JsonAnyGetter
来获取值。 JSON 中的字段和值可以重复,我想从 JSON 中获取所有内容并将其存储在地图中。但是,如果键中有重复项,则直接 Jackson 去实现存储最后一个值。
我有一个很大的 JSON 文件,里面有很多事件。我正在逐个事件地读取文件,这样整个 JSON 文件就不会存储在内存中。读取单个事件后,我检查事件类型,并根据类型将其分配给不同的 POJO。以下是我的示例 JSON 文件,其中包含 2 个事件。
[
{
"isA": "Type1",
"name": "Test",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
},
{
"isA": "Type2",
"name": "Test1",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
}
]
以下是用于反序列化的 class:(我有许多字段在反序列化期间直接映射并且工作正常,因此为简单起见省略)。这是 type1
事件的 class 如果它是 type2
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Type1
{
private String name;
private Map<String, Object> userExtensions;
@JsonAnyGetter
public Map<String, Object> getUserExtensions() {
return userExtensions;
}
@JsonAnySetter
public void setUserExtensions(String key, Object value) {
System.out.println("KEY : " + key + " VALUES : " + values);
}
}
正如您从上面看到的,我有一个 Map
字段,用于使用 JsonAnySetter
填充扩展。我有一个方法可以为 Jackson 无法直接搜索的字段调用。
我尝试在 @JsonAnySetter
方法中设置 System.out
,我观察到 Jackson 在此方法中没有得到重复字段:
@JsonAnySetter
public void setUserExtensions(String key, Object value) {
System.out.println(" Key : " + key + " Value : " + value);
}
我只得到其中的最后一个字段。对于上面提到的 JSON 我只得到最后一个 foo
其值为:
"foo" :{
"myField" : "Value1"
"myField" : "value2"
}
val1
和 val2
的前 2 个 foo
甚至不会在此方法中打印。
以下是我的 Main
方法,它实际上是罪魁祸首,因为我使用的是 Jackson 的 objectMapper.treeToValue
,它不支持重复字段。
public class Main
{
public static void main (String[]args)
{
//File where the JSON is stored
InputStream jsonStream = Main.class.getClassLoader().getResourceAsStream("InputEPCISEvents.json");
final JsonFactory jsonFactory = new JsonFactory();
final JsonParser jsonParser = jsonFactory.createParser (jsonStream);
jsonParser.setCodec (new ObjectMapper ());
final ObjectMapper objectMapper = new ObjectMapper();
// Loop until the end of the events file
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
// Get the node
final JsonNode jsonNode = jsonParser.readValueAsTree();
// Get the eventType
final String eventType = jsonNode.get("isA").toString().replaceAll("\"", "");
switch (eventType) {
case "Type1":
final Type1 objInfo = objectMapper.treeToValue(jsonNode, Type1.class);
break;
case "Type2":
final Type2 objInfo = objectMapper.treeToValue(jsonNode, Type2.class);
break;
default:
System.out.println("None of the event Matches");
break;
}
}
}
}
我想知道如何让 Jackson 读取重复的键值,以便我可以在 @JsonAnySetter
方法中处理它们。
我想知道是否有办法直接使用 Jackson 处理这些场景,或者我需要构建自己的自定义反序列化。如果是,那么如何为 class 中的一个字段构建一个?
PS:我尝试了很多在线可用的东西,但 none 有效,因此发布了相同的东西。如果发现重复那我真的很抱歉。
我注意到 Stack Overflow 上的大部分代码示例都使用 Jackson readValue
方法读取 JSON,但就我而言,我有一个包含许多事件的大型 JSON 文件所以我逐个事件拆分它们并使用 Jackson objectMapper.treeToValue
方法将其映射到我的 class。在这个 class 中,我尝试将每个事件的字段映射到各自的 class 字段,如果没有找到匹配项,那么我希望使用 @JsonAnySetter
方法填充它们。
有了这个测试数据:
{
"foo" : "val1",
"foo" : "val2",
"bar" : "val3",
"foo" :{
"myField" : "Value1",
"myField" : "value2"
}
}
此代码:
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class Main2 {
public static void main(String[] args) throws Exception {
File jsonFile = new File("test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
MyObject myObject = mapper.readValue(jsonFile, MyObject.class);
}
}
class MyObject {
// attributes
@JsonAnySetter
public void manyFoos(String key, Object value) {
System.out.println(" Key : " + key + " Value : " + value);
}
}
打印:
Key : foo Value : val1
Key : foo Value : val2
Key : bar Value : val3
Key : foo Value : {myField=value2}
问题是您在多个级别上有重复的键。使用此代码,您可以在外部级别处理重复项,并按照您的意愿进行操作,但 jackson 反序列化了值部分。在第三个 foo 的情况下,该值是一个映射,因此,重复项将丢失。
我想我有一个可能对您有用的解决方案,或者至少让您向前迈进了几步。我使用您的文件创建了一个本地测试来演示。
我通过查看 FAIL_ON_READING_DUP_TREE_KEY 的实现方式找到了一种配给它的方法。基本上,响应它的代码在 JsonNodeDeserializer 中。如果你看到这个反序列化器如何看待 _handleDuplicateField,它基本上只会用最新的节点替换最新的节点(你看到的副作用),如果你设置失败功能,它就会有条件地抛出异常。但是,通过使用覆盖方法创建此反序列化器 class 的扩展,我们基本上可以实现“合并欺骗”的行为,而不是“看到错误时抛出错误”。结果如下。
我测试的控制台日志 运行 产生了这个,表明“foos”包含所有独立的字符串和对象...
由于它在 JSON 级别而不是在对象级别进行合并,它似乎甚至通过将多个字符串合并到一个 arrayNode 中无意中处理了内部 myField 用例...我当时甚至没有尝试解决 myField,但是嘿,你也去吧!
我没有处理 Type2,因为我认为这足以让您继续前进。
当然,您必须在测试包中创建包含上述内容的文件 jsonmerge/jsonmerge。json
控制台日志结果
Object Node Read with Dupes: {"isA":"Type1","name":"Test","foo":["val1","val2",{"myField":["Value1","value2"]}],"bar":"val3"}
Type 1 mapped after merge : Type1 [isA=Type1, name=Test, bar=val3, foos=[val1, val2, {myField=[Value1, value2]}]]
None of the event Matches
EOF
测试主代码 - JsonMerger.java
package jsonmerge;
import java.io.InputStream;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Project to resolve
*
*/
public class JsonMerger {
public static void main(String[] args) throws Exception {
// File where the JSON is stored
InputStream jsonStream = JsonMerger.class.getClassLoader()
.getResourceAsStream("jsonmerge/jsonmerge.json");
final JsonFactory jsonFactory = new JsonFactory();
final ObjectMapper objectMapper = new ObjectMapper();
// Inject a deserializer that can handle/merge duplicate field declarations of whatever object(s) type into an arrayNode with those objects inside it
SimpleModule module = new SimpleModule();
module.addDeserializer(JsonNode.class, new JsonNodeDupeFieldHandlingDeserializer());
objectMapper.registerModule(module);
final JsonParser jsonParser = jsonFactory.createParser(jsonStream);
jsonParser.setCodec(objectMapper);
JsonToken nextToken = jsonParser.nextToken();
// Loop until the end of the events file
while (nextToken != JsonToken.END_ARRAY) {
nextToken = jsonParser.nextToken();
final JsonNode jsonNode = jsonParser.readValueAsTree();
if (jsonNode == null || jsonNode.isNull()) {
System.out.println("EOF");
break;
}
// Get the eventType
JsonNode getNode = jsonNode.get("isA");
final String eventType = getNode
.toString()
.replaceAll("\"", "");
switch (eventType) {
case "Type1":
final Object obj = objectMapper.treeToValue(jsonNode, JsonNodeDupeFieldHandlingDeserializer.class);
ObjectNode objNode = (ObjectNode) obj;
System.out.println();
System.out.println();
System.out.println("Object Node Read with Dupes: " + objNode);
Type1 type1 = objectMapper.treeToValue(objNode, Type1.class);
System.out.println("Type 1 mapped after merge : " + type1);
break;
default:
System.out.println("None of the event Matches");
break;
}
}
}
}
Type1.java
package jsonmerge;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonAnySetter;
public class Type1 {
public String isA;
public String name;
public String bar;
public List<Object> foos;
public Type1() {
foos = new LinkedList<Object>();
}
/**
* Inspired by
*
* @param key
* @param value
*/
@JsonAnySetter
public void fieldSetters(String key, Object value) {
// System.out.println("key = " + key + " -> " + value.getClass() + ": " + value);
// Handle duplicate "foo" fields to merge in strings/objects into a List<Object>
if ("foo".equals(key) && value instanceof String) {
foos.add((String) value);
} else if ("foo".equals(key) && (value instanceof Collection<?>)) {
foos.addAll((Collection<?>) value);
}
}
@Override
public String toString() {
return "Type1 [isA=" + isA + ", name=" + name + ", bar=" + bar + ", foos=" + foos + "]";
}
}
JsonNodeDupeFieldHandlingDeserializer
package jsonmerge;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Handles JsonNodes by merging their contents if their values conflict into an arrayNode
*
*/
@JsonDeserialize(using = JsonNodeDupeFieldHandlingDeserializer.class)
public class JsonNodeDupeFieldHandlingDeserializer extends JsonNodeDeserializer {
private static final long serialVersionUID = 1L;
@Override
protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt, JsonNodeFactory nodeFactory, String fieldName, ObjectNode objectNode, JsonNode oldValue, JsonNode newValue) throws JsonProcessingException {
// When you encounter a duplicate field, instead of throwing an exception in this super-logic... (if the feature was set anyway)
// super._handleDuplicateField(p, ctxt, nodeFactory, fieldName, objectNode, oldValue, newValue);
// Merge the results into a common arrayNode
// Note, this assumes that multiple values will combine into an array...
// And *THAT* array will be used for future nodes to be tacked onto...
// But if the FIRST thing to combine *IS* an array, then it will be the
// initial array...
// You could probably persist some way to track whether the initial array was encountered, but don't want to think about that now..
ArrayNode asArrayValue = null;
if (oldValue.isArray()) {
asArrayValue = (ArrayNode) oldValue;
} else {
// If not, create as array for replacement and add initial value..
asArrayValue = nodeFactory.arrayNode();
asArrayValue.add(oldValue);
}
asArrayValue.add(newValue);
objectNode.set(fieldName, asArrayValue);
}
}
我正在使用 Jackson 库反序列化 JSON。在 JSON 中,我有一些自定义字段,其值可以是任何值,因此我尝试使用 @JsonAnySetter
和 @JsonAnyGetter
来获取值。 JSON 中的字段和值可以重复,我想从 JSON 中获取所有内容并将其存储在地图中。但是,如果键中有重复项,则直接 Jackson 去实现存储最后一个值。
我有一个很大的 JSON 文件,里面有很多事件。我正在逐个事件地读取文件,这样整个 JSON 文件就不会存储在内存中。读取单个事件后,我检查事件类型,并根据类型将其分配给不同的 POJO。以下是我的示例 JSON 文件,其中包含 2 个事件。
[
{
"isA": "Type1",
"name": "Test",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
},
{
"isA": "Type2",
"name": "Test1",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
}
]
以下是用于反序列化的 class:(我有许多字段在反序列化期间直接映射并且工作正常,因此为简单起见省略)。这是 type1
事件的 class 如果它是 type2
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Type1
{
private String name;
private Map<String, Object> userExtensions;
@JsonAnyGetter
public Map<String, Object> getUserExtensions() {
return userExtensions;
}
@JsonAnySetter
public void setUserExtensions(String key, Object value) {
System.out.println("KEY : " + key + " VALUES : " + values);
}
}
正如您从上面看到的,我有一个 Map
字段,用于使用 JsonAnySetter
填充扩展。我有一个方法可以为 Jackson 无法直接搜索的字段调用。
我尝试在 @JsonAnySetter
方法中设置 System.out
,我观察到 Jackson 在此方法中没有得到重复字段:
@JsonAnySetter
public void setUserExtensions(String key, Object value) {
System.out.println(" Key : " + key + " Value : " + value);
}
我只得到其中的最后一个字段。对于上面提到的 JSON 我只得到最后一个 foo
其值为:
"foo" :{
"myField" : "Value1"
"myField" : "value2"
}
val1
和 val2
的前 2 个 foo
甚至不会在此方法中打印。
以下是我的 Main
方法,它实际上是罪魁祸首,因为我使用的是 Jackson 的 objectMapper.treeToValue
,它不支持重复字段。
public class Main
{
public static void main (String[]args)
{
//File where the JSON is stored
InputStream jsonStream = Main.class.getClassLoader().getResourceAsStream("InputEPCISEvents.json");
final JsonFactory jsonFactory = new JsonFactory();
final JsonParser jsonParser = jsonFactory.createParser (jsonStream);
jsonParser.setCodec (new ObjectMapper ());
final ObjectMapper objectMapper = new ObjectMapper();
// Loop until the end of the events file
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
// Get the node
final JsonNode jsonNode = jsonParser.readValueAsTree();
// Get the eventType
final String eventType = jsonNode.get("isA").toString().replaceAll("\"", "");
switch (eventType) {
case "Type1":
final Type1 objInfo = objectMapper.treeToValue(jsonNode, Type1.class);
break;
case "Type2":
final Type2 objInfo = objectMapper.treeToValue(jsonNode, Type2.class);
break;
default:
System.out.println("None of the event Matches");
break;
}
}
}
}
我想知道如何让 Jackson 读取重复的键值,以便我可以在 @JsonAnySetter
方法中处理它们。
我想知道是否有办法直接使用 Jackson 处理这些场景,或者我需要构建自己的自定义反序列化。如果是,那么如何为 class 中的一个字段构建一个?
PS:我尝试了很多在线可用的东西,但 none 有效,因此发布了相同的东西。如果发现重复那我真的很抱歉。
我注意到 Stack Overflow 上的大部分代码示例都使用 Jackson readValue
方法读取 JSON,但就我而言,我有一个包含许多事件的大型 JSON 文件所以我逐个事件拆分它们并使用 Jackson objectMapper.treeToValue
方法将其映射到我的 class。在这个 class 中,我尝试将每个事件的字段映射到各自的 class 字段,如果没有找到匹配项,那么我希望使用 @JsonAnySetter
方法填充它们。
有了这个测试数据:
{
"foo" : "val1",
"foo" : "val2",
"bar" : "val3",
"foo" :{
"myField" : "Value1",
"myField" : "value2"
}
}
此代码:
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class Main2 {
public static void main(String[] args) throws Exception {
File jsonFile = new File("test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
MyObject myObject = mapper.readValue(jsonFile, MyObject.class);
}
}
class MyObject {
// attributes
@JsonAnySetter
public void manyFoos(String key, Object value) {
System.out.println(" Key : " + key + " Value : " + value);
}
}
打印:
Key : foo Value : val1
Key : foo Value : val2
Key : bar Value : val3
Key : foo Value : {myField=value2}
问题是您在多个级别上有重复的键。使用此代码,您可以在外部级别处理重复项,并按照您的意愿进行操作,但 jackson 反序列化了值部分。在第三个 foo 的情况下,该值是一个映射,因此,重复项将丢失。
我想我有一个可能对您有用的解决方案,或者至少让您向前迈进了几步。我使用您的文件创建了一个本地测试来演示。
我通过查看 FAIL_ON_READING_DUP_TREE_KEY 的实现方式找到了一种配给它的方法。基本上,响应它的代码在 JsonNodeDeserializer 中。如果你看到这个反序列化器如何看待 _handleDuplicateField,它基本上只会用最新的节点替换最新的节点(你看到的副作用),如果你设置失败功能,它就会有条件地抛出异常。但是,通过使用覆盖方法创建此反序列化器 class 的扩展,我们基本上可以实现“合并欺骗”的行为,而不是“看到错误时抛出错误”。结果如下。
我测试的控制台日志 运行 产生了这个,表明“foos”包含所有独立的字符串和对象...
由于它在 JSON 级别而不是在对象级别进行合并,它似乎甚至通过将多个字符串合并到一个 arrayNode 中无意中处理了内部 myField 用例...我当时甚至没有尝试解决 myField,但是嘿,你也去吧!
我没有处理 Type2,因为我认为这足以让您继续前进。
当然,您必须在测试包中创建包含上述内容的文件 jsonmerge/jsonmerge。json
控制台日志结果
Object Node Read with Dupes: {"isA":"Type1","name":"Test","foo":["val1","val2",{"myField":["Value1","value2"]}],"bar":"val3"}
Type 1 mapped after merge : Type1 [isA=Type1, name=Test, bar=val3, foos=[val1, val2, {myField=[Value1, value2]}]]
None of the event Matches
EOF
测试主代码 - JsonMerger.java
package jsonmerge;
import java.io.InputStream;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Project to resolve
*
*/
public class JsonMerger {
public static void main(String[] args) throws Exception {
// File where the JSON is stored
InputStream jsonStream = JsonMerger.class.getClassLoader()
.getResourceAsStream("jsonmerge/jsonmerge.json");
final JsonFactory jsonFactory = new JsonFactory();
final ObjectMapper objectMapper = new ObjectMapper();
// Inject a deserializer that can handle/merge duplicate field declarations of whatever object(s) type into an arrayNode with those objects inside it
SimpleModule module = new SimpleModule();
module.addDeserializer(JsonNode.class, new JsonNodeDupeFieldHandlingDeserializer());
objectMapper.registerModule(module);
final JsonParser jsonParser = jsonFactory.createParser(jsonStream);
jsonParser.setCodec(objectMapper);
JsonToken nextToken = jsonParser.nextToken();
// Loop until the end of the events file
while (nextToken != JsonToken.END_ARRAY) {
nextToken = jsonParser.nextToken();
final JsonNode jsonNode = jsonParser.readValueAsTree();
if (jsonNode == null || jsonNode.isNull()) {
System.out.println("EOF");
break;
}
// Get the eventType
JsonNode getNode = jsonNode.get("isA");
final String eventType = getNode
.toString()
.replaceAll("\"", "");
switch (eventType) {
case "Type1":
final Object obj = objectMapper.treeToValue(jsonNode, JsonNodeDupeFieldHandlingDeserializer.class);
ObjectNode objNode = (ObjectNode) obj;
System.out.println();
System.out.println();
System.out.println("Object Node Read with Dupes: " + objNode);
Type1 type1 = objectMapper.treeToValue(objNode, Type1.class);
System.out.println("Type 1 mapped after merge : " + type1);
break;
default:
System.out.println("None of the event Matches");
break;
}
}
}
}
Type1.java
package jsonmerge;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonAnySetter;
public class Type1 {
public String isA;
public String name;
public String bar;
public List<Object> foos;
public Type1() {
foos = new LinkedList<Object>();
}
/**
* Inspired by
*
* @param key
* @param value
*/
@JsonAnySetter
public void fieldSetters(String key, Object value) {
// System.out.println("key = " + key + " -> " + value.getClass() + ": " + value);
// Handle duplicate "foo" fields to merge in strings/objects into a List<Object>
if ("foo".equals(key) && value instanceof String) {
foos.add((String) value);
} else if ("foo".equals(key) && (value instanceof Collection<?>)) {
foos.addAll((Collection<?>) value);
}
}
@Override
public String toString() {
return "Type1 [isA=" + isA + ", name=" + name + ", bar=" + bar + ", foos=" + foos + "]";
}
}
JsonNodeDupeFieldHandlingDeserializer
package jsonmerge;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Handles JsonNodes by merging their contents if their values conflict into an arrayNode
*
*/
@JsonDeserialize(using = JsonNodeDupeFieldHandlingDeserializer.class)
public class JsonNodeDupeFieldHandlingDeserializer extends JsonNodeDeserializer {
private static final long serialVersionUID = 1L;
@Override
protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt, JsonNodeFactory nodeFactory, String fieldName, ObjectNode objectNode, JsonNode oldValue, JsonNode newValue) throws JsonProcessingException {
// When you encounter a duplicate field, instead of throwing an exception in this super-logic... (if the feature was set anyway)
// super._handleDuplicateField(p, ctxt, nodeFactory, fieldName, objectNode, oldValue, newValue);
// Merge the results into a common arrayNode
// Note, this assumes that multiple values will combine into an array...
// And *THAT* array will be used for future nodes to be tacked onto...
// But if the FIRST thing to combine *IS* an array, then it will be the
// initial array...
// You could probably persist some way to track whether the initial array was encountered, but don't want to think about that now..
ArrayNode asArrayValue = null;
if (oldValue.isArray()) {
asArrayValue = (ArrayNode) oldValue;
} else {
// If not, create as array for replacement and add initial value..
asArrayValue = nodeFactory.arrayNode();
asArrayValue.add(oldValue);
}
asArrayValue.add(newValue);
objectNode.set(fieldName, asArrayValue);
}
}