概述

在这个教程中,我们将探讨使用JsonNode从嵌套JSON中提取所有键的不同方法。我们将目标是遍历JSON字符串,并将键名收集到一个列表中。

引言

Jackson库使用树模型来表示JSON数据。这种树模型为我们提供了与层次化数据交互的高效方式。

JSON对象在树模型中表示为节点,这使得对JSON内容执行CRUD操作更加便捷。

2.1. ObjectMapper

我们使用ObjectMapper类的方法读取JSON内容。ObjectMapper.readTree()方法将JSON反序列化并构建一个JsonNode实例的树。它接受JSON源作为输入,并返回创建的树模型的根节点。然后,我们可以使用根节点遍历整个JSON树。

树模型不仅限于读取常规的Java对象。JSON字段和树模型之间有一对一的映射。因此,无论是POJO还是非POJO的对象,都可以表示为节点。这样,我们就可以采用灵活的方法以通用节点的形式表示JSON内容

要了解更多,请参考我们的Jackson ObjectMapper教程

2.2. JsonNode

JsonNode类代表JSON树模型中的一个节点。它可以表达以下数据类型:ArrayBinaryBooleanMissingNullNumberObjectPOJOString。这些数据类型由JsonNodeType枚举定义。

3. 从JSON获取键

在本文中,我们将使用以下JSON作为输入:

{
   "Name":"Craig",
   "Age":10,
   "BookInterests":[
      {
         "Book":"The Kite Runner",
         "Author":"Khaled Hosseini"
      },
      {
         "Book":"Harry Potter",
         "Author":"J. K. Rowling"
      }
   ],
   "FoodInterests":{
      "Breakfast":[
         {
            "Bread":"Whole wheat",
            "Beverage":"Fruit juice"
         },
         {
            "Sandwich":"Vegetable Sandwich",
            "Beverage":"Coffee"
         }
      ]
   }
}

这里我们使用String对象作为输入,但也可以从不同的来源读取JSON内容,如Filebyte[]URLInputStreamJsonParser等。

现在让我们讨论从JSON中获取键的不同方法。

3.1. 使用fieldNames()

我们可以在JsonNode实例上使用fieldNames()方法来获取嵌套字段的名称。它仅返回直接嵌套字段的名称。

让我们用一个简单的例子来尝试:

public List<String> getKeysInJsonUsingJsonNodeFieldNames(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    Iterator<String> iterator = jsonNode.fieldNames();
    iterator.forEachRemaining(e -> keys.add(e));
    return keys;
}

我们将得到以下键:

[Name, Age, BookInterests, FoodInterests]

为了获取所有内部嵌套节点,我们需要在每一层节点上调用fieldNames()方法

public List<String> getAllKeysInJsonUsingJsonNodeFieldNames(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    getAllKeysUsingJsonNodeFieldNames(jsonNode, keys);
    return keys;
}
private void getAllKeysUsingJsonNodeFields(JsonNode jsonNode, List<String> keys) {

    if (jsonNode.isObject()) {
        Iterator<Entry<String, JsonNode>> fields = jsonNode.fields();
        fields.forEachRemaining(field -> {
            keys.add(field.getKey());
            getAllKeysUsingJsonNodeFieldNames((JsonNode) field.getValue(), keys);
        });
    } else if (jsonNode.isArray()) {
        ArrayNode arrayField = (ArrayNode) jsonNode;
        arrayField.forEach(node -> {
            getAllKeysUsingJsonNodeFieldNames(node, keys);
        });
    }
}

首先,我们会检查JSON值是否为对象或数组。如果是,则也会遍历值对象以获取内联节点。

因此,我们将获取JSON中所有的键名:

[Name, Age, BookInterests, Book, Author,
  Book, Author, FoodInterests, Breakfast, Bread, Beverage, Sandwich, Beverage]

在上述示例中,我们也可以使用JsonNode类的fields()方法获取字段对象,而不仅仅是字段名称:

public List<String> getAllKeysInJsonUsingJsonNodeFields(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    getAllKeysUsingJsonNodeFields(jsonNode, keys);
    return keys;
}

private void getAllKeysUsingJsonNodeFields(JsonNode jsonNode, List<String> keys) {

    if (jsonNode.isObject()) {
        Iterator<Entry<String, JsonNode>> fields = jsonNode.fields();
        fields.forEachRemaining(field -> {
            keys.add(field.getKey());
            getAllKeysUsingJsonNodeFieldNames((JsonNode) field.getValue(), keys);
        });
    } else if (jsonNode.isArray()) {
        ArrayNode arrayField = (ArrayNode) jsonNode;
        arrayField.forEach(node -> {
            getAllKeysUsingJsonNodeFieldNames(node, keys);
        });
    }
}

3.2. 使用JsonParser

我们还可以使用JsonParser类进行低级JSON解析。JsonParser从给定的JSON内容创建一系列可迭代的令牌。令牌类型由JsonToken类中的枚举指定,如下所示:

  • NOT_AVAILABLE
  • START_OBJECT
  • END_OBJECT
  • START_ARRAY
  • FIELD_NAME
  • VALUE_EMBEDDED_OBJECT
  • VALUE_STRING
  • VALUE_NUMBER_INT
  • VALUE_NUMBER_FLOAT
  • VALUE_TRUE
  • VALUE_FALSE
  • VALUE_NULL

在使用JsonParser迭代时,我们可以检查令牌类型并执行所需的操作。让我们尝试从示例JSON字符串中获取所有字段名称:

public List<String> getKeysInJsonUsingJsonParser(String json, ObjectMapper mapper) throws IOException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    JsonParser jsonParser = jsonNode.traverse();
    while (!jsonParser.isClosed()) {
        if (jsonParser.nextToken() == JsonToken.FIELD_NAME) {
            keys.add((jsonParser.getCurrentName()));
        }
    }
    return keys;
}

这里,我们使用了JsonNode类的traverse()方法获取JsonParser对象。同样,我们也可以使用JsonFactory创建JsonParser对象:

public List<String> getKeysInJsonUsingJsonParser(String json) throws JsonParseException, IOException {

    List<String> keys = new ArrayList<>();
    JsonFactory factory = new JsonFactory();
    JsonParser jsonParser = factory.createParser(json);
    while (!jsonParser.isClosed()) {
        if (jsonParser.nextToken() == JsonToken.FIELD_NAME) {
            keys.add((jsonParser.getCurrentName()));
        }
    }
    return keys;
}

结果,我们将从示例JSON内容中获取所有提取的键名:

[Name, Age, BookInterests, Book, Author,
  Book, Author, FoodInterests, Breakfast, Bread, Beverage, Sandwich, Beverage]

注意,与本文中介绍的其他方法相比,代码有多么简洁。

3.3. 使用Map

我们可以使用ObjectMapper类的readValue()方法将JSON内容反序列化为Map。然后,我们可以在遍历Map对象时提取JSON元素。让我们尝试使用这种方法从示例JSON中获取所有键:

public List<String> getKeysInJsonUsingMaps(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {
    List<String> keys = new ArrayList<>();
    Map<String, Object> jsonElements = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
    });
    getAllKeys(jsonElements, keys);
    return keys;
}

private void getAllKeys(Map<String, Object> jsonElements, List<String> keys) {

    jsonElements.entrySet()
        .forEach(entry -> {
            keys.add(entry.getKey());
            if (entry.getValue() instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) entry.getValue();
                getAllKeys(map, keys);
            } else if (entry.getValue() instanceof List) {
                List<?> list = (List<?>) entry.getValue();
                list.forEach(listEntry -> {
                    if (listEntry instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) listEntry;
                        getAllKeys(map, keys);
                    }
                });
            }
        });
}

同样,在获取顶级节点后,我们还需要遍历那些值为对象(地图)或数组的JSON对象,以获取嵌套节点。

4. 结论

在这篇文章中,我们学习了从JSON内容中读取键名的不同方法。从此,我们可以扩展文章中讨论的遍历逻辑,根据需要对JSON元素执行其他操作。

如往常一样,示例代码的完整源代码可在GitHub上找到。