How to parse JSON to a map using lowercase keys using Jackson?

I use the Jackson library (1.9.x) to parse JSON to the map:

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

Is there a way to tell Jackson's parser about the lowercase of all key names? I tried using Jackson PropertyNamingStrategy, but it didn’t work - it seems that it is useful when it is displayed on some bean, but not on the map.

Explanations:

  • I don’t want you to create beans for JSON - I only need dynamic maps
  • Incoming JSON keys will not contain lowercase letters, but I want all the map keys to be lowercase (see example below)
  • JSON is quite large and heavily nested, so regular replacement replacements for incoming JSON or creating a new map manually after Jackson parsing is not required.

Incoming JSON:

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

A Java map will have:

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

[UPDATE] . The answer I gave below does not completely solve the problem. Still looking for a solution.

+4
source share
5 answers

I figured out how to do this. Use org.codehaus.jackson.map.KeyDeserializer , put it in SimpleModule and register this module with 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] : in fact, this will only cause the top-level keys of the map to be displayed, but not the nested keys.

If the input:

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

The output on the map, unfortunately, will be:

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

(nb this solution is tested only with Jackson 2)

This can be done by wrapping JsonParser and simply applying .toLowerCase() to all field names:

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

Then you must have a custom JsonFactory to apply your wrapper, as in this test:

 @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
source

With Jackson, there is no function that will omit keys in a nested manner. At least I don't know that. I wrote this simple recursive code that does the job.

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

It's also easy to get a Map<String, Object> instead of a JSONObject (this is what you want) by making resultJsonObject a Map type and other small settings.

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

+1
source
 public void setKeyName(String systemName){ this.systemName = systemName.toLowerCase(); } 
0
source

Below is the second JSON message:

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

Typically, ObjectMapper cannot by default deserialize this message into a CarInfo object. The following configuration is possible:

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

This deserialization is valid. its deserialization is valid.

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

0
source

Source: https://habr.com/ru/post/1502506/


All Articles