Reputation: 14624
I am using the Jackson (1.9.x) library to parse JSON into a 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.
Clarifications:
Incoming JSON:
{"CustName":"Jimmy Smith","Result":"foo","CustNo":"1234"}
The Java map would have:
"custname" => "Jimmy Smith"
"result" => "foo"
"custno" => "1234"
[UPDATE]: The answer I gave below doesn't fully solve the problem. Still looking for a solution.
Upvotes: 8
Views: 10616
Reputation: 1
Below is the second JSON message:
{
"ModeL":"Tesla",
"YeaR":"2015"
}
Normally, default ObjectMapper cannot deserialize this message into a CarInfo object. With following configuration, it’s 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. his deserialization is valid.
https://mtyurt.net/post/jackson-case-insensitive-deserialization.html
Upvotes: 0
Reputation: 11
public void setKeyName(String systemName){
this.systemName = systemName.toLowerCase();
}
Upvotes: 0
Reputation: 133752
(nb this solution is tested only with Jackson 2)
It's possible to do this by wrapping the 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();
}
}
You then have to 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"
)
)));
}
Upvotes: 5
Reputation: 8242
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.
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.
WARNING: for nested JSON, the result would be of type Map<String, Map<String, Object>>
depending on how nested is your json object.
Upvotes: 2
Reputation: 14624
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
.
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"}}
Upvotes: 4