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にあります。