概述
在这个教程中,我们将探讨使用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树模型中的一个节点。它可以表达以下数据类型:Array
、Binary
、Boolean
、Missing
、Null
、Number
、Object
、POJO
、String
。这些数据类型由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内容,如File
、byte[]
、URL
、InputStream
、JsonParser
等。
现在让我们讨论从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上找到。