Jackson将JSON转换为Map并将键名转换为驼峰命名

13
{"name":"John Doe","age":30,"emailAddress":"johndoe@example.com"}

to

{"name":"John Doe","age":30,"email_address":"johndoe@example.com"}
{
    "SomeKey": "SomeValue",
    "AnotherKey": "another value",
    "InnerJson" : {"TheKey" : "TheValue"}
}

到这个地步...

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

我的代码...

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>>(){});
}

但这不起作用...甚至其他的 propertyNamingStrategy 在 JSON 上也不起作用,例如...

{
    "someKey": "SomeValue"
}

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PascalCaseStrategy())

{
    "SomeKey": "SomeValue"
}

如何通过Jackson获取camelCase Map键名... 或者我是否需要手动循环Map并转换键名,或者是否有其他方法?

提前致谢...


1
这似乎与您正在尝试实现的内容类似:https://github.com/FasterXML/jackson-databind/issues/62 - Laurentiu L.
4个回答

9
由于您使用的是地图/字典而不是将JSON数据绑定到POJO(与JSON数据匹配的显式Java类),因此属性命名策略不适用:

Class PropertyNamingStrategy ... 定义了如何从POJO方法和字段的名称(“内部名称”)派生JSON属性的名称(“外部名称”)

因此,您需要首先使用Jackson解析数据,然后迭代结果并转换键。
请按照以下方式更改您的代码:
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);
}

并添加以下方法:

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
非常感谢您抽出时间来注意到这是属性命名策略未应用于非POJO对象的问题。我正在处理一个遗留项目,因此通过POJO更改映射并不是一个选项。然而,建议的方法确实非常有帮助,我通过进行一些调整成功地获得了正确的映射。 - dic19
这个救了我的命。我以为我做错了什么!顺便说一句,Guava的CaseFormat非常有用,可以用于字符串转换,而不必重写mapKey。https://github.com/google/guava/wiki/StringsExplained#caseformat - Andrea Bergonzo

8

您总是可以迭代地遍历映射的键并对其进行更新。但是,如果您只想生成一个以驼峰命名法为键的JSON,则可以考虑下面描述的方法。

您可以使用自定义键序列化程序。将在将Map实例序列化为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);
    }
}

那么请按照以下步骤操作:
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);

输出结果将为:
{
  "someKey" : "SomeValue",
  "anotherKey" : "another value",
  "innerJson" : {
    "theKey" : "TheValue"
  }
}

采用这种方法,Map的键不会采用驼峰命名法,但它能为您提供所需的输出结果。

这是最快速、最完整的解决方案。我能够构建我的自定义序列化器。谢谢。 - Revol89

0

参考 @codo 的回答,我修改了另一个版本

 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;
  }

0
以下代码将把任何“大小写格式”的 JSON 转换为使用驼峰式键:
/**
 * 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);
    }
}

... 而 CaseUtil 实现可能是这样的:

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;
    }

}

一个单元测试:
    @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);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接