GSON: How to get a case insensitive element from Json?

Solution 1:

Unfortunately, registering a FieldNamingStrategy with the GsonBuilder wouldn't do much good, as it translates only in the opposite-than-desired direction: from the Java field name to the JSON element name. It cannot be reasonably used for your purposes.

(In Detail:

The result of the translation request ends at FieldNamingStrategy.translateName(Field), where the translated name is used to get the associated JSON element from a JsonObject, which has a LinkedHashMap<String, JsonElement>, called members, mapping JSON element names to their associated values. The translated name is used as the parameter to the get(String) method of members, and Gson provides no mechanism for this final call to be made case insensitive.

The members map is populated with calls to JsonObject.add(String, JsonElement), made from Streams.parseRecursive(JsonReader), with the JSON element name retrieved from the JsonReader used as the key to 'members'. (JsonReader uses the characters exactly as they are in the JSON, with the exception where the escape character '\' is found.) Throughout this call stack, Gson provides no mechanism for the keys used to populate members to be altered, e.g., to be made all lower case or all upper case.

A FieldNamingPolicy works in the same way.)

A reasonable solution might be to simply use a custom deserializer, along the following lines.

input.json:

[
 {"field":"one"},
 {"Field":"two"},
 {"FIELD":"three"},
 {"fIElD":"four"}
]

Foo.java:

import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.Map.Entry;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class Foo
{
  public static void main(String[] args) throws Exception
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(MyClass.class, new MyTypeAdapter());
    Gson gson = gsonBuilder.create();
    MyClass[] myObjects = gson.fromJson(new FileReader("input.json"), MyClass[].class);
    System.out.println(gson.toJson(myObjects));
  }
}

class MyClass
{
  String field;
}

class MyTypeAdapter implements JsonDeserializer<MyClass>
{
  @Override
  public MyClass deserialize(JsonElement json, Type myClassType, JsonDeserializationContext context)
      throws JsonParseException
  {
    // json = {"field":"one"}
    JsonObject originalJsonObject = json.getAsJsonObject();
    JsonObject replacementJsonObject = new JsonObject();
    for (Entry<String, JsonElement> elementEntry : originalJsonObject.entrySet())
    {
      String key = elementEntry.getKey();
      JsonElement value = originalJsonObject.get(key);
      key = key.toLowerCase();
      replacementJsonObject.add(key, value);
    }
    return new Gson().fromJson(replacementJsonObject, MyClass.class);
  }
}

Alternatively, you could first process the raw JSON to alter all of the element names to be the same case, all lower or all upper. Then, pass the altered JSON to Gson for deserialization. This would of course slow down JSON processing.

If you're able to change Gson code for your project, then probably the part to change for the most efficient result is the call to name = nextString((char) quote); in JsonReader. Since nextString(char) is also used to get the JSON element value, I'd probably just make a copy of it for getting the name, and then make small changes to force the element names to all lower or all upper case. Of course, this approach then locks your project to one release of Gson, else you'd need to repeat this change to upgrade to a newer Gson release.

With Jackson, the situation appears unfortunately similar. Translations with a PropertyNamingStrategy work in unfortunately much the same way: they translate from the Java field name to the JSON element name. None of the available JsonParser.Feature alterations would customize a JsonParser to force JSON element names to all upper or all lower case.

Solution 2:

I faced the similar issue. I did this to get around the issue. (Replaced all the keys with their corresponding lowercase version and had all lower case fields in matching class). Hope this helps.

 input = input.replaceAll("\\s","");
        Matcher m = Pattern.compile("\"\\b\\w{1,}\\b\"\\s*:").matcher(input);
        StringBuilder sanitizedJSON = new StringBuilder();
        int last = 0;
        while (m.find()) {
            sanitizedJSON.append(input.substring(last, m.start()));
            sanitizedJSON.append(m.group(0).toLowerCase());
            last = m.end();
        }
        sanitizedJSON.append(input.substring(last));

        input = sanitizedJSON.toString();

Solution 3:

Unfortunately there doesn't seem to be a way in the current implementation to do this. If you look at the Gson source and more specifically at the JsonObject implementation you will see that the underlying data structure is a linked hash map. The get call simply invokes the get on the map, which in turn uses the hash code and equals method of your key to find the object you are looking for.

The only way around is to enforce some naming conventions for you keys. The easiest way would be to force all the keys to lowercase. If you need mixed case keys then you will have more difficulty and will need to write a more sophisticated algorithm for transforming the keys instead of simply calling jsonKey.toLowerCase().

Solution 4:

I stumbled across this question when I ran into an issue where a different naming convention was being used at the two endpoints and subsequently discovered a less invasive solution.

Gson does support setting a naming convention that is used when mapping from the Java model names to the JSON names, both when when serializing and deserializing. Use the setFieldNamingPolicy method of the builder to change this behavior.

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
Gson gson = gsonBuilder.create();

See here for a nice article on the subject, including an overview of the different policies.

This isn't really a case insensitive solution, but it does provide a way to work around many of the situations where the case is not matching up.