JavaのJacksonライブラリを使い、再帰的にJSONのキーを変換

Java 17

- ObjectMapper を使用してJSONを Map<String, Object> に変換
- Map のキーを keyMap に基づいて変更
- ネストされた Map は再帰処理で変換
- List 内に Map があれば再帰処理
- LinkedHashMap は挿入した順序を保持するために使用

## 方法１. 変換表のMapを宣言＆設定(プログラム内でput)

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.*;

public class JsonKeyConverter {
    public static void main(String[] args) throws Exception {
        String jsonStr = """
        {
          "name": "taro",
          "age": 23,
          "seibetu": "man",
          "hobby": {
            "outdoor": {
              "land": ["running", "walking"],
              "sea": ["swimming", "fishing"]
            },
			"items": [
        		  { "name1": "sato" },
        		  { "name2": "suzuki" }
        		]
            }
        }
        """;

        // 旧 → 新のキーの変換マップ
        Map<String, String> keyMap = new HashMap<>();
        keyMap.put("name", "full_name");
        keyMap.put("age", "years_old");
        keyMap.put("seibetu", "gender");
        keyMap.put("hobby", "interests");
        keyMap.put("outdoor", "outside_activities");
        keyMap.put("indoor", "inside_activities");
        keyMap.put("land", "on_land");
        keyMap.put("sea", "in_sea");
        keyMap.put("name1", "full_name1");
        keyMap.put("name2", "full_name2");

        // JSONをマップに変換
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> jsonMap = null;
		try {
			jsonMap = objectMapper.readValue(jsonStr, new TypeReference<>() {});
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

        // キーを変換
        Map<String, Object> convertedMap = convertKeys(jsonMap, keyMap);

        // JSONに戻す
        String newJsonStr = null;
		try {
			newJsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(convertedMap);
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        System.out.println(newJsonStr);
    }

    // 再帰的にキーを変換
    private static Map<String, Object> convertKeys(Map<String, Object> original, Map<String, String> keyMap) {
        Map<String, Object> converted = new LinkedHashMap<>();
        for (Map.Entry<String, Object> entry : original.entrySet()) {
            String newKey = keyMap.getOrDefault(entry.getKey(), entry.getKey()); // 変換マップにある場合は新キーを使用
            Object value = entry.getValue();

            if (value instanceof Map) {
                // 再帰処理
                value = convertKeys((Map<String, Object>) value, keyMap); 
            } else if (value instanceof List) {
                value = convertList((List<Object>) value, keyMap);
            }

            converted.put(newKey, value);
        }
        return converted;
    }

    // List内のMapも再帰的に処理
    private static List<Object> convertList(List<Object> list, Map<String, String> keyMap) {
        List<Object> convertedList = new ArrayList<>();
        for (Object item : list) {
            if (item instanceof Map) {
                 // 再帰的に変換
                convertedList.add(convertKeys((Map<String, Object>) item, keyMap));
            } else {
                convertedList.add(item);
            }
        }
        return convertedList;
    }
}
```


実行

```sh
{
  "full_name" : "taro",
  "years_old" : 23,
  "gender" : "man",
  "interests" : {
    "outside_activities" : {
      "on_land" : [ "running", "walking" ],
      "in_sea" : [ "swimming", "fishing" ]
    },
    "items" : [ {
      "sato" : "full_name1"
    }, {
      "suzuki" : "full_name2"
    } ]
  }
}
```

## 方法2. 変換表のMapを宣言＆設定(プロパティファイルから読み込み)
```:src/main/resources/key_mapping.properties
name=full_name
age=years_old
seibetu=gender
hobby=interests
outdoor=outside_activities
indoor=inside_activities
land=on_land
sea=in_sea
movie=
```

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.*;

public class JsonKeyConverter {
    public static void main(String[] args) throws Exception {
        String jsonStr = """
        {
          "name": "taro",
          "age": 23,
          "seibetu": "man",
          "hobby": {
            "outdoor": {
              "land": ["running", "walking"],
              "sea": ["swimming", "fishing"]
            },
			"items": [
        		  { "name1": "full_name1" },
        		  { "name2": "full_name2" }
        		]
            }
        }
        """;

        // 旧 → 新のキーの変換マップ
        Map<String, String> keyMap = loadKeyMappings("META-INF/spring/key_mapping");

        // JSONをマップに変換
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> jsonMap = null;
		try {
			jsonMap = objectMapper.readValue(jsonStr, new TypeReference<>() {});
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

        // キーを変換
        Map<String, Object> convertedMap = convertKeys(jsonMap, keyMap);

        // JSONに戻す
        String newJsonStr = null;
		try {
			newJsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(convertedMap);
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        System.out.println(newJsonStr);
    }

    // 再帰的にキーを変換
    private static Map<String, Object> convertKeys(Map<String, Object> original, Map<String, String> keyMap) {
        Map<String, Object> converted = new LinkedHashMap<>();
        for (Map.Entry<String, Object> entry : original.entrySet()) {
            String newKey = keyMap.getOrDefault(entry.getKey(), entry.getKey()); // 変換マップにある場合は新キーを使用
            Object value = entry.getValue();

            if (value instanceof Map) {
                value = convertKeys((Map<String, Object>) value, keyMap); // 再帰処理
            } else if (value instanceof List) {
                value = convertList((List<Object>) value, keyMap);
            }

            converted.put(newKey, value);
        }
        return converted;
    }

    // List内のMapも再帰的に処理
    private static List<Object> convertList(List<Object> list, Map<String, String> keyMap) {
        List<Object> convertedList = new ArrayList<>();
        for (Object item : list) {
            if (item instanceof Map) {
                convertedList.add(convertKeys((Map<String, Object>) item, keyMap));
            } else {
                convertedList.add(item);
            }
        }
        return convertedList;
    }
    
    private static Map<String, String> loadKeyMappings(String baseName) {
        Map<String, String> keyMap = new HashMap<>();
    
        try {
            ResourceBundle bundle = ResourceBundle.getBundle(baseName);
            for (String key : bundle.keySet()) {
                String value = bundle.getString(key);
                
                if (StringUtils.isEmpty(value)) {
                    throw new IllegalArgumentException("エラー: キー '" + key + "' の値が空です。プロパティファイルを修正してください。");
                }
    
                keyMap.put(key, value);
            }
        } catch (MissingResourceException e) {
            System.err.println("エラー: プロパティファイル '" + baseName + ".properties' が見つかりません。");
            throw e; // そのまま例外を投げて処理を中断
        }
    
        return keyMap;
    }
}
```


実行

```sh
{
  "full_name" : "taro",
  "years_old" : 23,
  "gender" : "man",
  "interests" : {
    "outside_activities" : {
      "on_land" : [ "running", "walking" ],
      "in_sea" : [ "swimming", "fishing" ]
    },
    "items" : [ {
      "sato" : "full_name1"
    }, {
      "suzuki" : "full_name2"
    } ]
  }
}
```