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

Code shown below works well when JSON object contains jsonKey as it was passed to the method. I wonder ... if there is a way to get a value assigned to a case insensitive representation of a key?
Example:
public String getOutputEventDescription(JsonElement outputEvent) throws ParserException {
return retrieveString(outputEvent, DESCRIPTION);
}
Should work regardless whether DESCRIPTION is defined as "Description", "description" or "DeScRipTIOn"
protected String retrieveString(JsonElement e, String jsonKey) throws ParserException {
JsonElement value = e.getAsJsonObject().get(jsonKey);
if (value == null) {
throw new ParserException("Key not found: " + jsonKey);
}
if (value.getAsString().trim().isEmpty()) {
throw new ParserException("Key is empty: " + jsonKey);
}
return e.getAsJsonObject().get(jsonKey).getAsString();
}

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.

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

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().

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.

Related

Gson converts the HashMap<Integer> to HashMap<String> inside my Object

I have an object which has a map inside it:
MyDTO
HashMap<Integer>
Now when I convert my MyDTO to JSON (with Gson), and then back from JSON to MyDTO what I get is HashMap<String>.
I convert from JSON back to object like this:
MyDTO dto = gson.fromJson(json, MyDTO.class);
How can I force it to convert/keep the Map inside the DTO as Map<Integer> and NOT as Map<String>?
Here is my Object:
public class MultiSeriesTimebasedChartDTO implements Serializable
 {
LinkedHashMap<String, ArrayList<Number>> data;
}
Here's how I convert my JSON back to object:
multiSeriesTimebasedChartDTO = gson.fromJson(json, MultiSeriesTimebasedChartDTO.class);
And here is the result (in screenshot), which were Numbers but now are Strings. I needed them back as Numbers.
So looking for a clean approach for this.
I can definitely iterate over it, change every number from string back to number, and replace it... But I was thinking may be there is some other better way of oing it.
The values are still java.lang.Numbers after the JSON is parsed. However, because your field has the type LinkedHashMap<String, ArrayList<Number>>, Gson uses its internal type LazilyParsedNumber because it cannot know as which specific type you want the Numbers to be parsed. LazilyParsedNumber acts as a wrapper for the JSON string representation so you can call the respective Number method to parse the value.
LazilyParsedNumber should suffice if you are only retrieving its intValue(), doubleValue() ..., but if want to compare it with other Numbers it wont work since LazilyParsedNumber is only equal to itself.
Since your question mentions that the Map contains Integer values, the easiest solution would be to change the type of the DTO field:
LinkedHashMap<String, ArrayList<Integer>>
This way Gson knows the exact number type you want and can properly deserialize the JSON numbers as Integers.
You have no "HashMap<Integer>" whatever that could be, you have ArrayList<Number>, and that is what GSON has to prepare for:
public class MultiSeriesTimebasedChartDTO implements Serializable{
LinkedHashMap<String, ArrayList<Number>> data;
^^^^^^^^^^^^^^^^^
}
Also, you don't have Strings what you complain about, those are LazilyParsedNumbers,
And while it really stores the value as a string, that class indeed is a Number. You don't have to worry about its private member variables.
public final class LazilyParsedNumber extends Number { // <= extends Number
private final String value; // <= this is what you see in the debugger
But that is just the explanation about what's there now. If you want GSON to produce you a list of Integers, you should simply write that:
public class MultiSeriesTimebasedChartDTO implements Serializable{
LinkedHashMap<String, ArrayList<Integer>> data;
}
remember that GSON can only analyse the declaration of the class, it can't guess if you later ensure that all those generic numbers are integers.

JsonMappingException - Unexpected Character in field name

I am trying to deserialize JSON of format
"{mapping:{MyType(type=\"A\", value=\"B\"):\"C\"}}"
into a class type
class MyMapping {
Map<MyType, String> mapping;
}
class MyType {
String type;
String value;
}
Since I have a map type, i added a KeyDeserializer to deserialize MyType(type=\"A\", value=\"B\")
But when i try to deserialize this i get an exception
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('(' (code 40)): was expecting a colon to separate field name and value
This is because it encounters ( in MyType(type=\"A\", value=\"B\") and it breaks there. If i put the whole key within double quotes \"MyType(type=\"A\", value=\"B\")\", it passes the the whole key to my deserializer.
Is there a way i can force it to escape the '(' char without having to put the whole key with quotes.
The string in your example is not valid JSON.
"{mapping:{MyType(type=\"A\", value=\"B\"):\"C\"}}"
You pointed out that the Jackson parser complains about the first (
If you want to use this particular string as a key you have to enclose it in quotes and escape internal quotes. Let's use a Javascript in a browser to do that, cause it's easy. From Chrome console...
> var c = {};
< undefined
> c['MyType(type="A", value="B")']='C';
< "C"
> JSON.stringify(c)
< "{"MyType(type=\"A\", value=\"B\")":"C"}"`
That's how Javascript escapes your key string as valid JSON.
KeyDeserializer
Based on Jackson 2.8 documentation for KeyDeserializer I would expect your KeyDeserializer to receive the whole key string. That's just how JSON works.
{"key-string":"value")
Of course "value" could be false, true, null or a number as well.
It's then up to you to parse the key-string and decide how to turn it into a key that your MyMapping can use.
Your Key String Appears Easy to Parse
Your KeyDeserializer can use a compiled regex pattern to get the two key properties as two regex 'groups'.
What if you don't want to parse the key string?
It sounds like you don't want to parse this key. There are several ways to avoid this. One key to this is to use a JSON format that makes it easy, such as ...
{"key":{"keyType":"A","keyValue":"B"}, "value":"C"}
Then you could use annotations to teach Jackson to work with your MyMapping class.
Such as (untested, just a sketch)
class MyMapping {
Map<MyType, String> mapping;
#JsonCreator
public MyMapping(Map<String,Object> input) {
Map<String,Object> key = (Map)input.get("key");
String value = (String)input.get("value");
MyType myKey = new MyType(key);
mapping = new HashMap<>();
mapping.put(myKey,value);
}
}
class MyType {
String type;
String value;
MyType (Map<String,Object> input) {
type = (String)input.getOrDefault("key","");
value = (String)input.getOrDefault("value","");
}
}
There's a very long list of problems with this example. You will be using the default object address based comparison in the Map, for example. I made this code short by ignoring likely ClassCastExceptions and other data problems. But the point here was not to write good code, or to try to understand the requirements, but simply to point out how you can get Jackson to parse the JSON for you.
There are many. other ways to achieve that. I just. picked the first one that came to mind.

Convert String (http json) to Object in java (automatic)

I'm a beginner in programming, and I need some help. Is it possible to convert an HTTP (that returns a Json) automatic call to object in java? For example it reads the request, and when I call System.out.println (obj) it already returns me an OBJECT of this request, instead of String. Is it possible? If so, could you help me ... I already did the method to call the url and return string, but I need to return OBJECT, so I can compare with HashCode and Equals.
My code:
enter image description here
output:
{"header":{"messageId":"02938ec7-b2c3-4131-8ecf-3ad3a8509b41"},"body":{"products"
What I wanted: output
Informacoes [header=Header [messageId=66d22c00-bddc-4ea7-afbd-7c7225fcb914], body=Body
From what I can understand from your question, it looks like Gson might be useful. Gson is a library that allows you to convert between JSON and Java primitives/objects. Here's an example I just wrote:
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
}
public static void main(String[] args) {
Gson gson = new Gson();
String json = "{\"value1\":1,\"value2\":\"abc\"}";
BagOfPrimitives obj = gson.fromJson(json, BagOfPrimitives.class);
}
This code converts the json {"value1":1,"value2":"abc"} into an object of the class BagOfPrimitives.
To add Gson to your project, go here, click "Downloads" at the top right, and click "jar". Then follow these instructions to add the jar file to your project. Then you should be able to write import com.google.gson.* at the top of your class and use Gson in your java code.

Serializing Gson single element array as a simple String

I need to know if there is an easier way to serialize a single element Json array as a simple string using Gson. The requirements I have is this: I have an object which is an array of strings. If the array contains only one element, when it gets serialized into JSON, I don't want it represented as an array - I want it represented as a string. So, for example, if this array contains 2 elements, it should look like this:
[
"Something",
"Nothing"
]
If the array contains only one element, it should be rendered like this:
"Something"
The code below is a sample class that demonstrates the solution I found. If you leave all of the code un-commented, you will get a 2 element array correctly rendered in Json. If you comment out the following line you will get a single string:
testArray.add("Nothing");
The problem that I have with this solution is that it seems "clunky". I have to create a separate object of "Object" to make this work, and this seems unnecessary. I have also read that it is possible to do this using a custom serializer, but this seems like a lot of code too for such a small thing. Is there something built-in to Gson (or another library) which can do what I am trying to do?
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
public class DemoClassSerialize {
public static void main(String[] args) {
Object testObject = new Object();
JsonArray testArray = new JsonArray();
testArray.add("Something");
//comment out the next line to test the output when there is only one element in this array
testArray.add("Nothing");
if (testArray.size()==1) {
testObject=testArray.get(0);
} else {
testObject=testArray;
}
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
String gsonString=gson.toJson(testObject);
System.out.println(gsonString);
}
}

How do I correctly use generics?

I basically am making webrequests and recieving a JSON response. Depending on the request, I am parsing JSON request into objects I have created. The parsing is pretty much the same no matter what the object Im parsing into looks like. So I have a bunch of methods doing the same work only with different objects, I was wondering how I could accomplish this with generics? Here is an example
public static ArrayList<Contact> parseStuff(String responseData) {
ArrayList<Person> People = new ArrayList<Person>();
try {
JSONArray jsonPeople = new JSONArray(responseData);
if (!jsonPeople.isNull(0)) {
for (int i = 0; i < jsonPeople.length(); i++) {
People.add(new Person(jsonPeople.getJSONObject(i)));
}
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
}
return People;
}
You should look at Effective Java 2, Item 29: Consider typesafe heterogeneous type containers
The basic idea is that you could imagine you have some Deserializer interface. Then you can have a Map<Class<?>, Deserializer<String, Object>>. The map is basically a registry from type to the proper Deserializer for that type (which deserializes from a String (perhaps you want a json type or something instead of String, but String works) to the type of interest.
The real trick is that you must enforce that the class key type matches the deserializer type. i.e. - For some Class that is a key in the map, you have a Deserializer as a value. You can do this with a generic method. For example:
<T> void put(Class<T> clazz, Deserializer<String, T> deserializer) {
map.put(clazz, deserializer);
}
Then to use the heterogeneous map, you can use a generic method again:
<T> T deserialize(Class<T> typeToDeserializeFromJson, String toDeserialize) {
return typeToDeserializeFromJson.cast(
deserializers.get(tTDFJ).deserialize(toDeserialize));
}
You use the type that is specified by the method caller to
lookup the right `Deserializer`
deserialize the json text to an `Object`
safely cast the `Object` to the right type which we know is safe because of how we constructed the map in the first place.
I hope what I said was at least somewhat clear. Reading the corresponding entry in EJ2 should help!
UPDATE:
As Abhinav points out, the GSON library will help you accomplish your final goal of deserializing objects in a way that uses generics appropriately. However, I think your question is more about generics than the end goal so I feel my answer is more appropriate. GSON must be implemented using a heterogeneous type container :-).
Thanks, Abhinav for pointing out GSON!
I would suggest you use GSON instead of JSON. It really simplifies your life.

Categories