有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何在嵌套映射中使用Jackson KeyDeserializer?

问题

在现有的应用程序中,我必须反序列化可能的深度嵌套映射,并在所有级别的所有键上应用自定义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: Expecting: <{"three"=4}> to contain key: <"THREE">

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

Cannot deserialize instance of java.util.LinkedHashMap out of VALUE_NUMBER_INT token


问题

在本例中,即使需要将所有嵌套映射声明为Object,但在所有嵌套映射中获取大写键的最佳方法是什么

有没有其他方法来解决这个问题


共 (1) 个答案

  1. # 1 楼答案

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

    下面的解决方案使用JsonDeserializer而不是KeyDeserializer。我以您的示例为例,通过一个更复杂的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进行处理