GSON not de-serializing properly nested map with generic key/value - java

I'm using GSON to deserialise a JSON string to a JAVA object containing a nested Map with generic key/value.
From my debugging I see GSON converts the first generic type to the correct object. But the second is not converted and therefore acts as a string.
class A {
public B<C_Enum> b = new B<C_Enum>();
}
class B<T> {
private Map<T, T> map = new HashMap<T, T>();
}
enum C_Enum {
VAL1, VAL2;
}
main() {
String json = "{\"b\": {\"map\": {\"NOT_VALID\": \"NOT_VALID\"}}}";
GsonBuilder builder = new GsonBuilder();
Gson customDeserializer = builder.create();
A a = customDeserializer.fromJson(json, A.class);
}
In this example the map is populated with a null key because NOT_VALID is not a valid C_Enum value, and the value of the map is populated with NOT_VALID.
Note: my real code is a bit different but the problem is the same

I meet the same issue
Map<SOME_ENUM, T>map;
// gson will not use #SerializedName in the SOME_ENUM
String badJson = new Gson().toJson(map)
change it to
Map<SOME_ENUM, T>map;
// gson is good
String goodJson = new GsonBuilder().enableComplexMapKeySerialization().create().toJson(map)
link to gson/issue

Related

How to convert List<?> to List<Object> in java?

How to convert List<?> to List in java?
For example I have this class
#Data
public class Example {
private List<?> data;
}
and I used in this function
#PostMapping("/getResult")
#ResponseBody
public Result getResult(#RequestBody String json) {
Gson gson = new Gson();
Example xmpl = gson.fromJson(json, Example.class);
List<MyObject> source = (List<MyObject>)xmpl.getData(); //==> error
// get Result
return result;
}
It will give this error
class com.google.gson.internal.LinkedTreeMap cannot be cast to class com.myproject.MyObject
EDITED:
The real problem is not from converting ? to object, but from converting LinkedTreeMap to the object
WORKAROUND :
String jsonData = gson.toJson(xmpl.getData());
MyObjectBean[] objs = gson.fromJson(jsonData,MyObjectBean[].class);
You could go with two solutions, to start with:
You can change the Generic type, this way You don't say data is any collection, but it's a collection of type <T>. Now You can create classes with given type anywhere You need it.
Generic value <?> means in general that you don't care what is inside, and probably You won't read it anyway. When You are interested only if collection is null or what it's size.
When You need to do something with it, then use Generic types.
Example:
public class Example<T> {
private List<T> data;
}
Now inside of your controller, create a private class, to deserialize your payload.
static class MyObjectExample extends Example<MyObject>{
}
Now you can use it do decode JSON:
MyObjectExample xmpl = gson.fromJson(json, MyObjectExample.class);
List<MyObject> source = xmpl.getData();
Now if your code can be serialized to MyObject it will work.
Spring supports deserialization also.
If you have a #RestController annotation added to your Controller class
Example:
#PostMapping("/getResult")
public Result getResult(#RequestBody MyObjectExample xmpl) {
// get Result
return result;
}
Or you can add
consumes = MediaType.APPLICATION_JSON_VALUE
to your REST method.
Try using Spring to convert your value for you.
You can find more
GetMapping and PostMapping
tutotrial
The real issue is not when converting ? to MyObject, but the LinkedTreeMap to MyObject, from
this explanation
by #harsh
so I did this workaround
String jsonData = gson.toJson(xmpl.getData());
MyObjectBean[] objs = gson.fromJson(jsonData,MyObjectBean[].class);

Gson deserialize abstract class in kotlin [duplicate]

I have a tree object in JSON format I'm trying to deserialize with Gson. Each node contains its child nodes as fields of object type Node. Node is an interface, which has several concrete class implementations. During the deserialization process, how can I communicate to Gson which concrete class to implement when deserializing the node, if I do not know a priori which type the node belongs to? Each Node has a member field specifying the type. Is there a way to access the field when the object is in serialized form, and somehow communicate the type to Gson?
Thanks!
I'd suggest adding a custom JsonDeserializer for Nodes:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
You will be able to access the JsonElement representing the node in the deserializer's method, convert that to a JsonObject, and retrieve the field that specifies the type. You can then create an instance of the correct type of Node based on that.
You will need to register both JSONSerializer and JSONDeserializer. Also you can implement a generic adapter for all your interfaces in the following way:
During Serialization : Add a META-info of the actual impl class type.
During DeSerialization : Retrieve that meta info and call the JSONDeserailize of that class
Here is the implementation that I have used for myself and works fine.
public class PropertyBasedInterfaceMarshal implements
JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_META_KEY = "CLASS_META_KEY";
#Override
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Object object, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
object.getClass().getCanonicalName());
return jsonEle;
}
}
Then you could register this adapter for all your interfaces as follows
Gson gson = new GsonBuilder()
.registerTypeAdapter(IInterfaceOne.class,
new PropertyBasedInterfaceMarshal())
.registerTypeAdapter(IInterfaceTwo.class,
new PropertyBasedInterfaceMarshal()).create();
As far as I can tell this doesn't work for non-collection types, or more specifically, situations where the concrete type is used to serialize, and the interface type is used to deserialize. That is, if you have a simple class implementing an interface and you serialize the concrete class, then specify the interface to deserialize, you'll end up in an unrecoverable situation.
In the above example the type adapter is registered against the interface, but when you serialize using the concrete class it will not be used, meaning the CLASS_META_KEY data will never be set.
If you specify the adapter as a hierarchical adapter (thereby telling gson to use it for all types in the hierarchy), you'll end up in an infinite loop as the serializer will just keep calling itself.
Anyone know how to serialize from a concrete implementation of an interface, then deserialize using only the interface and an InstanceCreator?
By default it seems that gson will create the concrete instance, but does not set it's fields.
Issue is logged here:
http://code.google.com/p/google-gson/issues/detail?id=411&q=interface
I want to correct the above a little
public class PropertyMarshallerAbstractTask implements JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_TYPE = "CLASS_TYPE";
#Override
public Object deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_TYPE).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Object object, Type type, JsonSerializationContext jsonSerializationContext) {
Gson gson = new Gson(); //without this line it will not work
gson.toJson(object, object.getClass()); //and this one
JsonElement jsonElement = gson.toJsonTree(object); //it needs to replace to another method...toJsonTree
jsonElement.getAsJsonObject().addProperty(CLASS_TYPE, object.getClass().getCanonicalName());
return jsonElement;
}
}
And then I use it:
Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractTask.class, new PropertyMarshallerOfAbstractTask())
.create();
And then I can parse List (where I keep some non-abstract classes, which inherited from Abstract Task) to Json;
And it works in the opposite direction
List<AbstractTask> abstractTasks = gson.fromJson(json, new TypeToken<List<AbstractTask>>(){}.getType());
You have to use TypeToken class from Google Gson.
You will need of course has a generic class T to make it works
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);

Gson serialize a List field to string and preserve SerializedName

I want to serialize a List<String> field to String while preserving its #SerializedName.
For instance - this is my pojo
Class Friend {
#SerializedName("friendName", alternate = "name")
String name;
#SerializedName("friendlyNames", alternate = "akaNames")
List<String> nicknames
}
I want to serialize this as
{
"friendName": "Friend name goes here",
"friendlyNames": "name1:name2:name3"
}
Is it possible to use an annotation on top of nicknames and invoke a custom serializer?
I looked at typeAdaper but I don't see where I can get the SerializedName attribute in that.
The JsonSerializer field doesn't seem to be applicable at field level.
Turns out I didn't need to know the JsonSerialized name value. I was able to do this and test it out and it worked fine.
public class MyCustomSerializer implements JsonSerializer<List<String>> {
private final String CUSTOM_STRING_DELIMITER = ":";
#Override
public JsonElement serialize(List<String> strings, Type type, JsonSerializationContext jsonSerializationContext) {
String result = String.join(CUSTOM_STRING_DELIMITER, strings);
return new JsonPrimitive(result);
}
}
later on do this
Type listOfStrings = new TypeToken<List<String>>() {}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(listOfStrings, new MyCustomSerializer())
.create();

Deserialize a class containing java.lang.CharSequence member variable

I have a simple class containing name variable of type java.lang.CharSequence
class Person {
public java.lang.CharSequence name;
}
When I try to deserialize a JSON String using GSON library
Person p;
Gson gson = new Gson();
String json = "{\"name\":\"dinesh\"}";
p = gson.fromJson(json, Person.class);
System.out.println(p);
It gives me the following error:
java.lang.RuntimeException: Unable to invoke no-args constructor for interface java.lang.CharSequence. Registering an InstanceCreator with Gson for this type may fix this problem.
How do I fix this? I cannot change the Person.name type to String.
As suggested in comments,
I created a custom adapter for CharSequence
class CharSequenceAdapter extends TypeAdapter<CharSequence> {
#Override
public void write(JsonWriter out, CharSequence value) throws IOException {
}
#Override
public CharSequence read(JsonReader in) throws IOException {
String s = new String();
in.beginObject();
while(in.hasNext()) {
s = in.nextString();
}
return s;
}
}
And my GSON builder looks like this:
Person p;
GsonBuilder builder = new GsonBuilder().registerTypeAdapter(java.lang.CharSequence.class, new CharSequenceAdapter());
Gson gson = builder.create();
String json = "{\"name\":\"dinesh\"}";
p = gson.fromJson(json, Person.class);
System.out.println(p);
Now it gives me another error:
Expected BEGIN_OBJECT but was STRING at line 1 column 10 path $.name
What did I miss?
I don't think it's a duplicate. All the other questions talk about deserializing one class or interface as a whole. I am having a problem with a class that has interface references as member variables. I couldn't solve the problem from similar answers.
CharSequence is an interface.
When Gson tries to deserialize a json string into an object it “introspects” the object (by using reflection) and tries to resolve the types of fields.
Then it tries to create a field of that time.
Out of the box Gson can deal with many “widespread” types like String, Integer, Boolean and so forth, however when its something GSon is not aware of (Like CharSequence in this case), GSon stops with Error.
Now its clear that you should “teach” Gson to understand this custom type.
For this purpose there are type adapters in Gson.
Here you can find a short tutorial on using the adapters.
I won’t re-write here an example from there, but in general you should create an adapter, register it on Gson object and call your code. When Gson reaches the CharSequence field it will find this custom adapter and invoke it
As mentioned in the other answers, Gson has no built-in adapter for CharSequence (see related pull request) and is therefore unable to deserialize it.
However, you can solve this by writing a custom TypeAdapter, such as the following:
class CharSequenceTypeAdapter extends TypeAdapter<CharSequence> {
#Override
public void write(JsonWriter out, CharSequence value) throws IOException {
if (value == null) {
out.nullValue();
} else {
// Assumes that value complies with CharSequence.toString() contract
out.value(value.toString());
}
}
#Override
public CharSequence read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
// Skip the JSON null
in.skipValue();
return null;
} else {
return in.nextString();
}
}
}
This assumes that your CharSequence value is encoded as JSON string value (e.g. "value") and not as JSON object ({ ... }). For serialization it also assumes that the value you are using complies with the CharSequence.toString() contract.
You have to register the adapter then with a GsonBuilder which you use to create the Gson instance:
Gson gson = new GsonBuilder()
.registerTypeAdapter(CharSequence.class, new CharSequenceTypeAdapter())
.create();
Alternatively, if you actually always use String as CharSequence value then you could also change the field types to String, in case that does not have any negative effect on the API of your code.
You don't need the in.beginObject(); line. Without that line, the code works fine.

Gson-> Json to deserialize List of custom objects

I am trying to serialize and de-serialize an ArrayList of Java POJOs using Gson on Json objects
I have an object MyClass as
public class MyClass{
private int type
private int pos;
private Object value;
}
I have an ArrayList of these objects, which I serialize as
List<MyClass> values= null;
String json = new Gson().toJson(retValues);
The json string is
[{"type":4,"pos":1,"value":15}]
I try to deserialize it as
Type myType = new TypeToken<ArrayList<MyClass>>() {}.getType();
List<MyClass> test=new Gson().fromJson(json, myType);
I get an error
The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#1141ddf failed to deserialized json object [{"type":4,"pos":1,"value":18}] given the type java.util.ArrayList<abc.MyClass>
Any input much appreciated!
I figured it out. I added 2 things, I don't know which one made it work.
- I added a no-arguments constructor to MyClass
- I made MyClass implement serializable.
And it works!
Thanks for your help.

Categories