JsonNodeを使用してJSON文字列のすべてのキーを取得する
1. 概要
この記事では、 JSONNode を使用して、JSONからすべてのネストされたキーを抽出するさまざまな方法について説明します。 JSON文字列をトラバースし、リストにキー名を収集することを目的としています。
2. 序章
Jackson ライブラリは、ツリーモデルを使用してJSONデータを表します。 ツリーモデルは、階層データと対話するための効率的な方法を提供します。
JSONオブジェクトは、ツリーモデルのノードとして表されます。 これにより、JSONコンテンツでのCRUD操作の実行が容易になります。
2.1. ObjectMapper
ObjectMapper クラスメソッドを使用して、JSONコンテンツを読み取ります。 ObjectMapper.readTree()メソッドはJSONを逆シリアル化し、JsonNodeインスタンスのツリーを構築します。 JSONソースを入力として受け取り、作成されたツリーモデルのルートノードを返します。 その後、ルートノードを使用してJSONツリー全体をトラバースできます。
ツリーモデルは、通常のJavaオブジェクトの読み取りのみに限定されません。 JSONフィールドとツリーモデルの間には1対1のマッピングがあります。 これにより、POJOであるかどうかに関係なく、各オブジェクトをノードとして表すことができます。 したがって、JSONコンテンツを汎用ノードとして表す柔軟なアプローチを利用できます。
詳細については、JacksonObjectMapperの記事を参照してください。
2.2. JsonNode
JsonNode クラスは、JSONツリーモデルのノードを表します。 JSONデータは次のデータ型で表現できます: Array、Binary、Boolean、Missing、Null、Number、Object、POJO、String。これらのデータ型は JsonNodeType [ X190X]enum。
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 オブジェクトを入力として使用していますが、 File 、 byte [] 、などのさまざまなソースからJSONコンテンツを読み取ることができます。 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クラスのenumsとして指定されます。
- 利用不可
- START_OBJECT
- END_OBJECT
- START_ARRAY
- フィールド名
- 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. マップを使用する
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のにあります。