Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Jackson@JsonAnySetter在与Jackson ObjectMapper treeToValue方法一起使用时忽略重复键的值_Java_Json_Jackson_Json Deserialization_Jackson2 - Fatal编程技术网

Java Jackson@JsonAnySetter在与Jackson ObjectMapper treeToValue方法一起使用时忽略重复键的值

Java Jackson@JsonAnySetter在与Jackson ObjectMapper treeToValue方法一起使用时忽略重复键的值,java,json,jackson,json-deserialization,jackson2,Java,Json,Jackson,Json Deserialization,Jackson2,我正在使用Jackson库反序列化JSON。在JSON中,我有几个自定义字段,其值可以是任何值,因此我尝试使用@JsonAnySetter和@JsonAnyGetter来获取值。JSON中的字段和值可以复制,我希望从JSON中获取所有内容并将其存储在映射中。但是,如果密钥中存在重复项,则直接Jackson去序列化将存储最后一个值 我有一个包含许多事件的大型JSON文件。我正在逐个事件地读取文件,这样整个JSON文件就不会存储在内存中。读取单个事件后,我检查事件的类型,并根据该类型将其分配给不同的

我正在使用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"
    }
  }
]
下面是用于反序列化的类:(我有许多字段在反序列化过程中直接映射,并且工作正常,因此为了简单起见省略了这些字段)。这是
type1
事件的类,如果它是
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);
    }
    
}
我只得到这个字段的最后一个字段。对于上述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处理这些场景,或者我需要构建自己的自定义反序列化。如果是,那么如何为类中的一个字段构建一个字段

PS:我在网上尝试了很多东西,但是没有一件有效,因此我发布了相同的东西。如果发现重复,我真的很抱歉

我注意到,堆栈溢出上的大多数代码示例都使用Jackson
readValue
方法读取JSON,但在我的例子中,我有一个包含许多事件的大型JSON文件,因此我将它们逐事件拆分,并使用Jackson
objectMapper.treeToValue
方法将其映射到我的类。在这个类中,我尝试将每个事件的字段映射到相应的类字段,如果没有找到匹配项,我希望使用
@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。基本上,响应它的代码在JSONNodeSerializer中。如果您看到这个反序列化器如何看待_handledUpplicateField,它基本上只会用最新的节点替换最新的节点(您看到的副作用),如果您设置了fail功能,它将有条件地抛出异常。但是,通过使用重写方法创建此反序列化器类的扩展,我们基本上可以实现“合并重复项”的行为,而不是“看到错误时抛出错误”。结果如下

我的测试运行中的控制台日志产生了这个结果,证明“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 https://stackoverflow.com/questions/67413028/jackson-jsonanysetter-ignores-values-of-duplicate-key-when-used-with-jackson-ob
 *
 */
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;
            }

        }

    }
}
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 https://stackoverflow.com/questions/61528937/deserialize-duplicate-keys-to-list-using-jackson
     * 
     * @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 + "]";
    }

}
Type1.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 https://stackoverflow.com/questions/67413028/jackson-jsonanysetter-ignores-values-of-duplicate-key-when-used-with-jackson-ob
 *
 */
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;
            }

        }

    }
}
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 https://stackoverflow.com/questions/61528937/deserialize-duplicate-keys-to-list-using-jackson
     * 
     * @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 + "]";
    }

}

感谢您花时间回复。实际上,我在更新的代码中尝试过这种方法,我在方法
setUserExtensions
中也提供了同样的东西,它只提供了最后一个
键及其
,因此我丢失了以前重复的
键值
。我注意到这里的大多数代码示例包括您的,请使用
Jackson readValue
阅读JSON,但在我的例子中,我有一个包含许多事件的大型JSON文件,因此我将它们逐事件拆分,并使用
Jackson objectMapper.treeToValue
方法将其映射到我的类。这可能是我遗漏了重复键的旧值的原因吗?@BATMAN_2008,
treeToValue
方法只是问题的一部分。要调用它,您需要提供
TreeNode
,在
JSON对象的情况下,它是
ObjectNode
。此类不支持重复字段,当您将
JSON
反序列化到
TreeNode
时,有关重复字段的信息丢失。您只有一种方法可以修复它,那就是将
JSON
负载直接反序列化到
POJO
@MichałZiober非常感谢您的回答。实际上,我有一个包含许多事件的大型JSON文件。我必须检查传入事件的类型,并根据它将其映射到不同的
POJO
。由于文件很大,我不想存储