【问题标题】:How do I parse JSON into a Map with lowercase keys using Jackson?如何使用 Jackson 将 JSON 解析为带有小写键的 Map?
【发布时间】:2013-09-16 22:05:26
【问题描述】:

我正在使用 Jackson (1.9.x) 库将 JSON 解析为 Map:

ObjectMapper mapper = new ObjectMapper();
Map<String,Object> map = (Map<String,Object>) mapper.readValue(jsonStr, Map.class);

有没有办法告诉 Jackson 解析器将所有键名小写?我尝试使用 Jackson PropertyNamingStrategy,但这不起作用 - 它似乎只有在映射到某个 bean 时才有用,而不是 Map。

说明:

  1. 我不想为 JSON 预先创建 bean - 我只想要动态地图
  2. 传入的 JSON 键不会是小写,但我希望所有映射键都是小写(请参见下面的示例)
  3. JSON 相当大且嵌套严重,因此完全不需要在 Jackson 解析后对传入的 JSON 进行正则表达式替换或手动创建新地图。

传入的 JSON:

{"CustName":"Jimmy Smith","Result":"foo","CustNo":"1234"}

Java 地图将具有:

"custname" => "Jimmy Smith"
"result" => "foo"
"custno" => "1234"

[UPDATE]:我在下面给出的答案并不能完全解决问题。仍在寻找解决方案。

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

    (nb 此解决方案仅使用 Jackson 2 进行测试)

    可以通过包装 JsonParser 并简单地将 .toLowerCase() 应用于所有字段名称来做到这一点:

    private static final class DowncasingParser extends JsonParserDelegate {
        private DowncasingParser(JsonParser d) {
            super(d);
        }
    
        @Override
        public String getCurrentName() throws IOException, JsonParseException {
            if (hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
                return delegate.getCurrentName().toLowerCase();
            }
            return delegate.getCurrentName();
        }
    
        @Override
        public String getText() throws IOException, JsonParseException {
            if (hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
                return delegate.getText().toLowerCase();
            }
            return delegate.getText();
        }
    }
    

    然后你必须有一个自定义的 JsonFactory 来应用你的包装器,就像在这个测试中一样:

    @Test
    public void downcase_map_keys_by_extending_stream_parser() throws Exception {
        @SuppressWarnings("serial")
        ObjectMapper mapper = new ObjectMapper(new JsonFactory() {
            @Override
            protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException {
                return new DowncasingParser(super._createParser(data, offset, len, ctxt));
            }
    
            @Override
            protected JsonParser _createParser(InputStream in, IOContext ctxt) throws IOException {
                return new DowncasingParser(super._createParser(in, ctxt));
            }
    
            @Override
            protected JsonParser _createParser(Reader r, IOContext ctxt) throws IOException {
                return new DowncasingParser(super._createParser(r, ctxt));
            }
    
            @Override
            protected JsonParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable)
                    throws IOException {
                return new DowncasingParser(super._createParser(data, offset, len, ctxt, recyclable));
            }
        });
        assertThat(
                mapper.reader(Map.class)
                        .with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
                        .with(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
                        .readValue("{CustName:'Jimmy Smith', CustNo:'1234', Details:{PhoneNumber:'555-5555',Result:'foo'} } }"),
                equalTo((Map<String, ?>) ImmutableMap.of(
                        "custname", "Jimmy Smith",
                        "custno", "1234",
                        "details", ImmutableMap.of(
                                "phonenumber", "555-5555",
                                "result", "foo"
                                )
                        )));
    }
    

    【讨论】:

      【解决方案2】:

      我想出了一种方法。使用org.codehaus.jackson.map.KeyDeserializer,将其放入SimpleModule,然后将该模块注册到Jackson ObjectMapper

      import org.codehaus.jackson.map.KeyDeserializer;
      import org.codehaus.jackson.map.ObjectMapper;
      import org.codehaus.jackson.map.module.SimpleModule;
      import org.codehaus.jackson.Version;
      
      // ...
      
      class LowerCaseKeyDeserializer extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctx) 
            throws IOException, JsonProcessingException {
          return key.toLowerCase();
        }
      }
      
      // ...
      
      ObjectMapper mapper = new ObjectMapper();
      SimpleModule module = new SimpleModule("LowerCaseKeyDeserializer", 
                                             new Version(1,0,0,null));
      module.addKeyDeserializer(Object.class, new LowerCaseKeyDeserializer());
      mapper.registerModule(module);
      Map<String,Object> map = 
        (Map<String,Object>) mapper.readValue(jsonStr, Map.class);
      

      [UPDATE]:实际上这只会小写顶级映射键,而不是嵌套键。

      如果输入是:

      {"CustName":"Jimmy Smith","CustNo":"1234","Details":{"PhoneNumber": "555-5555", "Result": "foo"}}
      

      不幸的是,地图中的输出将是:

      {"custname"="Jimmy Smith", "custno"="1234", "details"={"PhoneNumber"="555-5555", "Result"="foo"}}
      

      【讨论】:

        【解决方案3】:

        对于 Jackson,没有任何功能可以以嵌套方式降低键。至少我知道的不是。我编写了这个简单的递归代码来完成这项工作。

            public JSONObject recursiveJsonKeyConverterToLower(JSONObject jsonObject) throws JSONException
            {
                JSONObject resultJsonObject = new JSONObject();
                @SuppressWarnings("unchecked") Iterator<String> keys = jsonObject.keys();
                while(keys.hasNext())
                {
                    String key = keys.next();
                    Object value = null;
                    try
                    {
                        JSONObject nestedJsonObject = jsonObject.getJSONObject(key);
                        value = this.recursiveJsonKeyConverterToLower(nestedJsonObject);
                    }
                    catch(JSONException jsonException)
                    {
                        value = jsonObject.get(key);
                    }
        
                    resultJsonObject.put(key.toLowerCase(), value);
                }
        
                return resultJsonObject;
            }
        

        传递的字符串:

        String json = "{'Music': 0, 'Books': {'Biology': 1.1, 'Chemistry': {'Inorganic': true, 'Organic': ['Atom', 'Molecule']}}, 'Food': {'Chicken': [1, 2, 3]}}";
        

        输出:

        {"music":0,"books":{"biology":1.1,"chemistry":{"inorganic":true,"organic":["Atom","Molecule"]}},"food":{"chicken":[1,2,3]}}
        

        通过将resultJsonObject 设置为Map 类型和其他一些小调整,也很容易得到Map&lt;String, Object&gt; 而不是JSONObject(这是你想要的)。

        警告:对于嵌套的 JSON,结果将是 Map&lt;String, Map&lt;String, Object&gt;&gt; 类型,具体取决于您的 json 对象的嵌套程度。

        【讨论】:

          【解决方案4】:
          public void setKeyName(String systemName){
              this.systemName = systemName.toLowerCase();
          }
          

          【讨论】:

            【解决方案5】:

            下面是第二条 JSON 消息:

            {
                "ModeL":"Tesla",
                "YeaR":"2015"
            }
            

            通常,默认 ObjectMapper 无法将此消息反序列化为 CarInfo 对象。通过以下配置,可以:

            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
            CarInfo info = objectMapper.readValue(data, CarInfo.class); //'data' contains JSON string
            

            这个反序列化是有效的。他的反序列化是有效的。

            https://mtyurt.net/post/jackson-case-insensitive-deserialization.html

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-01-19
              • 1970-01-01
              • 2017-04-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-11-11
              • 1970-01-01
              相关资源
              最近更新 更多