如何在嵌套映射中使用我的杰克逊密钥反序列化程序?



>问题

在现有应用程序中,我必须反序列化可能深度嵌套的映射,并在所有级别的所有键上应用自定义 Jackson 反序列化程序。

由于应用程序动态处理各种数据模型,因此我无法使用具有明确类型映射的显式数据模型...相反,我使用Map<String, Object>并检查反序列化的Object值本身是否为Map

这会导致自定义反序列化程序仅适用于顶级映射

更新:请务必注意,我无法将反序列化程序绑定到所有映射,因为我的数据模型通常也有更具体的映射。我通常有字段是开放式 JSON 设置的通用Map<String, Object>;与例如Map<EnumType, Double>更明确的配置位。

<小时 />

示例

为了说明这一点,我在下面提出了玩具示例:

ObjectMapper objectMapper = new ObjectMapper().registerModule(new SimpleModule()
.addKeyDeserializer(String.class, new KeyDeserializer() {
@Override
public Object deserializeKey(String s, DeserializationContext deserializationContext) {
return s.toUpperCase();
}
}));
Map<String, Object> value = objectMapper.readValue(
"{"zero": 1, "two": { "three": 4 }}",
new TypeReference<Map<String, Object>>() {}
);
assertThat(value).containsKeys("ZERO", "TWO"); // pass
assertThat((Map) value.get("TWO")).containsKey("THREE"); // fail

最后一行代码失败:

java.lang.AssertionError: 期待: <{"三"=4}> 要包含键: <"三">

我无法将类型声明为Map<String, Map<String, Object>>,因为 int 值处于同一级别。如果我尝试,我会得到:

无法从令牌中反序列化java.util.LinkedHashMap实例VALUE_NUMBER_INT

<小时 />

问题

在此示例中,即使在需要将它们声明为Object,我也可以在所有嵌套 Map 中获取大写键的最佳方法是什么?

有没有其他方法可以解决这个问题?

下面是一个可行的解决方案。 虽然它不使用 KeyDeserializer,但正如您所发现的,KeyDeserializer 不会深入到 JSON 中,将所有内部 JSON 字段名称大写。

下面的解决方案使用 Json解串程序而不是密钥反序列化程序。 我以您的示例为例,并通过了一个更复杂的 JSON 来测试它。 它遍历 JSON 中的所有对象,并将遇到的所有字段名称大写。

public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper().registerModule(
new SimpleModule()
.addDeserializer(Map.class, new JsonDeserializer<>() {
@Override
public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
var map = new HashMap<String, Object>();
while(!p.isClosed()){
JsonToken jsonToken = p.nextToken();
if (JsonToken.FIELD_NAME.equals(jsonToken)) {
String fieldName = p.getCurrentName().toUpperCase();
jsonToken = p.nextToken();
if (JsonToken.START_OBJECT.equals(jsonToken)) {
map.put(fieldName, getObject(p));
} else if (JsonToken.START_ARRAY.equals(jsonToken)) {
map.put(fieldName, getArray(p));
} else {
map.put(fieldName, getScalar(jsonToken, p));
}
}
}
return map;
}
})
);
Map<String, Object> value = objectMapper.readValue(
"{"zero": 1, "two": { "three": 4 }, "four": ["item", 5], "five": "string", "six": true, "seven": 1.2}",
new TypeReference<Map<String, Object>>() {}
);
assertThat(value).containsKeys("ZERO", "TWO"); // pass
assertThat((Map) value.get("TWO")).containsKey("THREE"); // pass
System.out.println("JSON = " + new GsonBuilder().setPrettyPrinting().create().toJson(value));
}
static Map<String, Object> getObject(JsonParser p) throws IOException {
var map = new HashMap<String, Object>();
JsonToken jt = p.nextToken();
while (!JsonToken.END_OBJECT.equals(jt)) {
if (JsonToken.FIELD_NAME.equals(jt)) {
String fieldName = p.getCurrentName().toUpperCase();
jt = p.nextToken();
if (JsonToken.START_OBJECT.equals(jt)) {
map.put(fieldName, getObject(p));
} else if (JsonToken.START_ARRAY.equals(jt)) {
map.put(fieldName, getArray(p));
} else {
map.put(fieldName, getScalar(jt, p));
}
}
jt = p.nextToken();
}
return map;
}
static List<Object> getArray(JsonParser p) throws IOException {
var list = new ArrayList<>();
JsonToken jt = p.nextToken();
while (!JsonToken.END_ARRAY.equals(jt)) {
if (JsonToken.START_OBJECT.equals(jt)) {
list.add(getObject(p));
} else if (JsonToken.START_ARRAY.equals(jt)) {
list.add(getArray(p));
} else {
list.add(getScalar(jt, p));
}
jt = p.nextToken();
}
return list;
}
static Object getScalar(JsonToken jsonToken, JsonParser p) throws IOException {
if (JsonToken.VALUE_NUMBER_INT.equals(jsonToken) || JsonToken.VALUE_NUMBER_FLOAT.equals(jsonToken)) {
return p.getNumberValue();
} else if (JsonToken.VALUE_FALSE.equals(jsonToken)) {
return false;
} else if (JsonToken.VALUE_TRUE.equals(jsonToken)) {
return true;
} else if (JsonToken.VALUE_STRING.equals(jsonToken)) {
return p.getValueAsString();
} else if (JsonToken.VALUE_NULL.equals(jsonToken)) {
return null;
}
throw new RuntimeException("did not find a scalar for JsonToken = " + jsonToken);
}

下面是它生成的输出。

JSON = {
"ZERO": 1,
"FIVE": "string",
"SIX": true,
"FOUR": [
"item",
5
],
"TWO": {
"THREE": 4
},
"SEVEN": 1.2
}

代码假定最外层的 JSON 对象是映射。 因此,它需要在 JsonDeserializer 中进行少量更新,以将数组或标量作为最外层的 JSON 进行处理。

最新更新