Jackson json to map and camelcase key name

19.6k views Asked by At

I want to convert json via jackson library to a map containing camelCase key...say...

from

{
    "SomeKey": "SomeValue",
    "AnotherKey": "another value",
    "InnerJson" : {"TheKey" : "TheValue"}
}

to this...

{
    "someKey": "SomeValue",
    "anotherKey": "another value",
    "innerJson" : {"theKey" : "TheValue"}
}

My Code...

public Map<String, Object> jsonToMap(String jsonString) throws IOException
{
    ObjectMapper mapper=new ObjectMapper();
    mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    return mapper.readValue(jsonString,new TypeReference<Map<String, Object>>(){});
}

But this doesn't work...even other propertyNamingStrategy does not work on json...such as...

{
    "someKey": "SomeValue"
}

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PascalCaseStrategy())

to

{
    "SomeKey": "SomeValue"
}

How to get the camelCase Map key name via jackson... or should I manually loop map and convert key or there are some other way???

Thanks in advance...

4

There are 4 answers

0
Kenneth On

The following will transform json with keys on any "case format" to using camelCased keys:

/**
 * Convert all property keys of the specified JSON to camelCase
 */
public static String toJsonWithCamelCasedKeys(String json) {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new SimpleModule()
      .addKeySerializer(String.class, new JsonSerializer<>() {
          @Override
          public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
              String key = CaseUtil.toCamelCase(value);
              gen.writeFieldName(key);
          }
      })
    );

    try {
        Map<String, Object> jsonMap = objectMapper.readValue(json, new TypeReference<>() {});
        return objectMapper.writeValueAsString(jsonMap);
    } catch (Exception e) {
        throw new JsonException("Error transforming JSON", e);
    }
}

...and a CaseUtil implementation could be something like this:

import java.util.Arrays;
import java.util.stream.Collectors;

public class CaseUtil {

    public static String toCamelCase(String s) {
        if (s == null) {
            return null;
        }
        else if (s.isBlank()) {
            return "";
        }

        return decapitaliseFirstLetter(
          String.join("", Arrays.stream(s.split("[-_\\s]"))
          .map(CaseUtil::capitaliseFirstLetter)
          .collect(Collectors.toList()))
        );
    }

    private static String capitaliseFirstLetter(String s) {
        return (s.length() > 0)
          ? s.substring(0, 1).toUpperCase() + s.substring(1)
          : s;
    }

    private static String decapitaliseFirstLetter(String s) {
        return (s.length() > 0)
          ? s.substring(0, 1).toLowerCase() + s.substring(1)
          : s;
    }

}

A unit test:

    @Test
    void jsonWithMiscCasedPropKeys_shouldConvertKeyToCamelCase() throws Exception {
        String inputJson =
            "{\"kebab-prop\": \"kebab\"," +
            "\"snake_prop\": \"snake\"," +
            "\"PascalProp\": \"pascal\"," +
            "\"camelCasedProp\": \"camel\"}";
        String expectedJson =
          "{\"kebabProp\": \"kebab\"," +
            "\"snakeProp\": \"snake\"," +
            "\"pascalProp\": \"pascal\"," +
            "\"camelCasedProp\": \"camel\"}";
        String actualJson = Json.toJsonWithCamelCasedKeys(inputJson);

        JSONAssert.assertEquals(expectedJson, actualJson, JSONCompareMode.LENIENT);
    }
0
kai On

ref @codo's answer, I modified another version

 public static Map<String, Object> snakeToCamelMap(Map<String, Object> map) {
    if (MapUtils.isEmpty(map)) {
      return map;
    }
    Map<String, Object> result = new HashMap<>();
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      String key = entry.getKey();
      Object value = entry.getValue();
      result.put(snakeToCamelString(key), snakeToCamel(value));
    }
    return result;
  }

  public static List<Object> snakeToCamelList(List<Object> list) {
    if (CollectionUtils.isEmpty(list)) {
      return list;
    }
    List<Object> result = new ArrayList<>();
    for (Object o : list) {
      result.add(snakeToCamel(o));
    }
    return result;
  }

  public static Object snakeToCamel(Object obj) {
    if (obj instanceof Map) {
      return snakeToCamelMap((Map<String, Object>) obj);
    } else if (obj instanceof List) {
      return snakeToCamelList((List) obj);
    } else {
      return obj;
    }
  }

  private static String snakeToCamelString(String key) {
    if (key.contains("_")) {
      return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, key);
    }
    return key;
  }
2
Codo On

As you are working with maps/dictionaries instead of binding the JSON data to POJOs (explicit Java classes that match the JSON data), the property naming strategy does not apply:

Class PropertyNamingStrategy ... defines how names of JSON properties ("external names") are derived from names of POJO methods and fields ("internal names")

Therefore, you have to first parse the data using Jackson and then iterate over the result and convert the keys.

Change your code like this:

public Map<String, Object> jsonToMap(String jsonString) throws IOException
{
    ObjectMapper mapper=new ObjectMapper();
    mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    Map<String, Object> map = mapper.readValue(jsonString,new TypeReference<Map<String, Object>>(){});
    return convertMap(map);
}

And add these methods:

public String mapKey(String key) {
    return Character.toLowerCase(key.charAt(0)) + key.substring(1);
}

public Map<String, Object> convertMap(Map<String, Object> map) {
    Map<String, Object> result = new HashMap<String, Object>();
    for (Map.Entry<String, Object> entry : map.entrySet()) {
        String key = entry.getKey();
        Object value = entry.getValue();
        result.put(mapKey(key), convertValue(value));
    }
    return result;
}

public convertList(Lst<Object> list) {
    List<Object> result = new ArrayList<Object>();
    for (Object obj : list) {
        result.add(convertValue(obj));
    }
    return result;
}

public Object covertValue(Object obj) {
    if (obj instanceof Map<String, Object>) {
        return convertMap((Map<String, Object>) obj);
    } else if (obj instanceof List<Object>) {
        return convertList((List<Object>) obj);
    } else {
        return obj;
    }
}
1
cassiomolin On

You always can iterate over the keys of the map and update them. However, if you are only interested in producing a JSON with camel case keys, you could consider the approach described below.

You could have a custom key serializer. It will be used when serializing a Map instance to JSON:

public class CamelCaseKeySerializer extends JsonSerializer<String> {

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException, JsonProcessingException {

        String key = Character.toLowerCase(value.charAt(0)) + value.substring(1);
        gen.writeFieldName(key);
    }
}

Then do as following:

String json = "{\"SomeKey\":\"SomeValue\",\"AnotherKey\":\"another value\",\"InnerJson\":"
            + "{\"TheKey\":\"TheValue\"}}";

SimpleModule simpleModule = new SimpleModule();
simpleModule.addKeySerializer(String.class, new CamelCaseKeySerializer());

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(simpleModule);

Map<String, Object> map = mapper.readValue(json, 
                                          new TypeReference<Map<String, Object>>() {});

String camelCaseJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);

The output will be:

{
  "someKey" : "SomeValue",
  "anotherKey" : "another value",
  "innerJson" : {
    "theKey" : "TheValue"
  }
}

With this approach, the keys of the Map won't be in camel case. But it will give you the desired output.