如何使用Jackson将JSON解析为带小写键的Map?

时间:2022-12-01 18:27:42

I am using the Jackson (1.9.x) library to parse JSON into a Map:

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

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

Is there a way to tell the Jackson parser to lowercase all the names of the keys? I tried using a Jackson PropertyNamingStrategy, but that didn't work - it only seems to be useful when it is getting mapped onto some bean, not a Map.

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

Clarifications:

  1. I do not want to have to precreate beans for the JSON - I only want dynamic Maps
  2. 我不想为JSON预先创建bean - 我只想要动态地图

  3. The JSON keys coming in will not be lowercase, but I want all the map keys to be lowercase (see example below)
  4. 进入的JSON键不会是小写,但我希望所有的地图键都是小写的(参见下面的示例)

  5. The JSON is rather large and heavily nested, so regular expression replacements of the incoming JSON or creating a new map manually after the Jackson parsing is not at all desired.
  6. JSON相当大并且嵌套很多,因此传入JSON的正则表达式替换或者在Jackson解析之后手动创建新地图根本不需要。

Incoming JSON:

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

The Java map would have:

Java地图将具有:

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

[UPDATE]: The answer I gave below doesn't fully solve the problem. Still looking for a solution.

[更新]:我在下面给出的答案并没有完全解决问题。仍在寻找解决方案。

4 个解决方案

#1


3  

I figured out one way to do it. Use a org.codehaus.jackson.map.KeyDeserializer, put it in a SimpleModule and register that module with the Jackson ObjectMapper.

我想出了一种方法。使用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]: Actually this only will lowercase the top level map keys, but not nested keys.

[更新]:实际上这只会使*地图键小写,而不是嵌套键。

If the input is:

如果输入是:

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

The output in the map, unfortunately, will be:

遗憾的是,地图中的输出将是:

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

#2


1  

With Jackson there isn't any function that will lower the keys in a nested fashion. Atleast not that I know of. I wrote this simple recursive code that does the job.

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

Passed String:

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

Output:

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

Its also easy to get Map<String, Object> instead of JSONObject (which is what you want) by making resultJsonObject to be of type Map and other little tweaks.

通过使resultJsonObject成为Map类型和其他小调整,它也很容易获得Map 而不是JSONObject(这是你想要的)。 ,object>

WARNING: for nested JSON, the result would be of type Map<String, Map<String, Object>> depending on how nested is your json object.

警告:对于嵌套的JSON,结果将是Map >类型,具体取决于json对象的嵌套方式。 ,map>

#3


1  

(nb this solution is tested only with Jackson 2)

(这个解决方案只在Jackson 2上测试过)

It's possible to do this by wrapping the JsonParser and simply applying .toLowerCase() to all field names:

可以通过包装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();
    }
}

You then have to have a custom JsonFactory to apply your wrapper, as in this test:

然后,您必须有一个自定义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"
                            )
                    )));
}

#4


0  

public void setKeyName(String systemName){
    this.systemName = systemName.toLowerCase();
}

#1


3  

I figured out one way to do it. Use a org.codehaus.jackson.map.KeyDeserializer, put it in a SimpleModule and register that module with the Jackson ObjectMapper.

我想出了一种方法。使用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]: Actually this only will lowercase the top level map keys, but not nested keys.

[更新]:实际上这只会使*地图键小写,而不是嵌套键。

If the input is:

如果输入是:

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

The output in the map, unfortunately, will be:

遗憾的是,地图中的输出将是:

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

#2


1  

With Jackson there isn't any function that will lower the keys in a nested fashion. Atleast not that I know of. I wrote this simple recursive code that does the job.

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

Passed String:

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

Output:

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

Its also easy to get Map<String, Object> instead of JSONObject (which is what you want) by making resultJsonObject to be of type Map and other little tweaks.

通过使resultJsonObject成为Map类型和其他小调整,它也很容易获得Map 而不是JSONObject(这是你想要的)。 ,object>

WARNING: for nested JSON, the result would be of type Map<String, Map<String, Object>> depending on how nested is your json object.

警告:对于嵌套的JSON,结果将是Map >类型,具体取决于json对象的嵌套方式。 ,map>

#3


1  

(nb this solution is tested only with Jackson 2)

(这个解决方案只在Jackson 2上测试过)

It's possible to do this by wrapping the JsonParser and simply applying .toLowerCase() to all field names:

可以通过包装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();
    }
}

You then have to have a custom JsonFactory to apply your wrapper, as in this test:

然后,您必须有一个自定义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"
                            )
                    )));
}

#4


0  

public void setKeyName(String systemName){
    this.systemName = systemName.toLowerCase();
}