Gson serialize a List field to string and preserve SerializedName - java

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

Related

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

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

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.

Unable to deserialize with RuntimeTypeAdapterFactory, " does not define a field named type"

I was trying to learn the concept of inheritance and deserialization of java beans through Gson framework. Details regarding java bean classes and json files are given below.
ParentBean.java
public class ParentBean {
protected String key1;
protected String key2;
public ParentBean(String key1, String key2) {
super();
this.key1 = key1;
this.key2 = key2;
}
}
Bean1.java
public class Bean1 extends ParentBean {
private String key3;
public Bean1(String key1, String key2, String key3) {
super(key1, key2);
this.key3 = key3;
}
}
Bean2.java
public class Bean2 extends ParentBean {
private String key4;
public Bean2(String key1, String key2, String key4) {
super(key1, key2);
this.key4 = key4;
}
}
bean1.json
{
"key1":"value1",
"key2":"value2",
"key3":"value33"
}
bean2.json
{
"key1":"value1",
"key2":"value2",
"key4":"value43"
}
To explore things about inheritance and deserialization, I have used the following code:
Usage.java
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
public class Usage {
public static void main(String[] args) throws FileNotFoundException {
RuntimeTypeAdapterFactory<ParentBean> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(ParentBean.class, "type")
.registerSubtype(Bean1.class, "bean1")
.registerSubtype(Bean2.class, "bean2");
Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
FileReader fr = new FileReader("bean1.json");
Type pType = new TypeToken<ParentBean>(){}.getType();
ParentBean pb = gson.fromJson(fr, pType);
if (pb instanceof Bean1) {
System.out.println(" Bean1");
} else if (pb instanceof Bean2) {
System.out.println("Bean2");
}
}
}
I am getting an error stack which is as follows:
Exception in thread "main" com.google.gson.JsonParseException: cannot deserialize class inheritance.ParentBean because it does not define a field named type
at com.google.gson.typeadapters.RuntimeTypeAdapterFactory$1.read(RuntimeTypeAdapterFactory.java:205)
at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:199)
at com.google.gson.Gson.fromJson(Gson.java:795)
at com.google.gson.Gson.fromJson(Gson.java:761)
at inheritance.Usage.main(Usage.java:23)
In search of finding solution, I came across this stack overflow discussion. Unfortunately the discussion was about create() method. Error stack says the problem was with line 23 and this line contains fromJson() method.
You need to tell gson more about the types. When serializing also the type needs to be serialized. So as the first comment by Jacob G. suggests you need the type field:
Docs for RuntimeTypeAdapterFactory.of(Class<T> baseType, String typeFieldName) states:
Creates a new runtime type adapter using for baseType using typeFieldName as the type field name. Type field names are case sensitive.
Add it to your ParentBean:
// Init it for serializing
// You used values like 'bean1' & 'bean2' but using class name is more generic
protected String type = getClass().getName();
According to above changes in bean type names change the building of RuntimeTypeAdapterFactory accordingly:
RuntimeTypeAdapterFactory<ParentBean> runtimeTypeAdapterFactory =
RuntimeTypeAdapterFactory
.of(ParentBean.class, "type") // typeFieldName
.registerSubtype(Bean1.class, Bean1.class.getName())
.registerSubtype(Bean2.class, Bean2.class.getName());
Finally - when de-serailizing - the Json files need also the type information which will be serialized from the field type so add it also fro both beans Json with correct package name:
"type":"org.example.gson.runtime.Bean1",
and
"type":"org.example.gson.runtime.Bean2",
You do not need to explicitly add a type field to your beans. As pirho suggests, you need to have the type field in your json string. If you're crafting this by hand, just add the type field, e.g.
{
"type":"bean1",
"key1":"value1",
"key2":"value2",
"key3":"value33"
}
Presumably, you're also serializing your objects and to achieve that, you'll need to specify the base class during serialization.
As swankjesse points out in
https://github.com/google/gson/issues/712
Try replacing this:
final String jsonStr = mGson.toJson(new Child());
With this:
final String jsonStr = mGson.toJson(new Child(), Base.class);
Then, when you serialize your types, your output json will be
{
"type":"bean1",
"key1":"value1",
"key2":"value2",
"key3":"value33"
}
That way, your beans can stay pure, without knowing that they are providing some type key to be used in serialization.

GSON TypeAdapter: DeSerialize Polymorphic Objects Based on "Type" Field

I'm using Retrofit with the default Gson parser for JSON processing. Oftentimes, I have a series of 4~5 related but slightly different objects, which are all subtypes of a common base (let's call it "BaseType"). I know we can deserialize the different JSONs to their respective child models by checking the "type" field. The most commonly prescribed way is to extend a JsonDeserializer and register it as a type adapter in the Gson instance:
class BaseTypeDeserializer implements JsonDeserializer<BaseType> {
private static final String TYPE_FIELD = "type";
#Override
public BaseType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject() && json.getAsJsonObject().has(TYPE_FIELD)) {
JsonObject jsonObject = json.getAsJsonObject();
final String type = jsonObject.get(TYPE_FIELD).getAsString();
if ("type_a".equals(type)) {
return context.deserialize(json, AType.class);
} else if ("type_b".equals(type)) {
return context.deserialize(json, BType.class);
} ...
// If you need to deserialize as BaseType,
// deserialize without the current context
// or you will infinite loop
return new Gson().fromJson(json, typeOfT);
} else {
// Return a blank object on error
return new BaseType();
}
}
}
However, in my experience this is really slow, and seemingly because we have to load up the entire JSON document into a JsonElement and then traverse it to find the type field. I also don't like it that this deserializer has to be run on every one of our REST calls, even though the data isn't always necessarily being mapped to a BaseType (or its children).
This foursquare blog post mentioned using TypeAdapters as an alternative but it didn't really go further with an example.
Anybody here know how to use TypeAdapterFactory to deserialize based on a 'type' field without having to read up the entire json stream into a JsonElement object tree?
The custom deserializer should only be run when you have a BaseType or a sub-classes in the deserialization data, not every request. You register it based on the type, and it is only called when gson need to serialize that type.
Do you deserialize BaseType as well as the sub-classes? If so, this line is going to kill your performance --
return new Gson().fromJson(json, typeOfT);
creation of new Gson objects is not cheap. You are creating one each time you deserialize a base class object. Moving this call to a constructor of BaseTypeDeserializer and stashing it in a member variable will improve performance (assuming you do deserialize the base class).
The issue with creating a TypeAdapter or TypeAdapterFactory for selecting type based on the field is that you need to know the type before you start consuming the stream. If the type field is part of the object, you cannot know the type at that point. The post you linked to mentions as much --
Deserializers written using TypeAdapters may be less flexible than
those written with JsonDeserializers. Imagine you want a type field to
determine what an object field deserializes to. With the streaming
API, you need to guarantee that type comes down in the response before
object.
If you can get the type before the object in the JSON stream, you can do it, otherwise your TypeAdapter implementation is probably going to mirror your current implementation, except that the first thing you do is convert to Json tree yourself so you can find the type field. That is not going to save you much over your current implementation.
If your subclasses are similar and you don't have any field conflicts between them (fields with the same name but different types), you can use a data transfer object that has all the fields. Use gson to deserialize that, and then use it create your objects.
public class MyDTO {
String type;
// Fields from BaseType
String fromBase;
// Fields from TypeA
String fromA;
// Fields from TypeB
// ...
}
public class BaseType {
String type;
String fromBase;
public BaseType(MyDTO dto) {
type = dto.type;
fromBase = dto.fromBase;
}
}
public class TypeA extends BaseType {
String fromA;
public TypeA(MyDTO dto) {
super(dto);
fromA = dto.fromA;
}
}
you can then create a TypeAdapterFactory that handles the conversion from DTO to your object --
public class BaseTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
if(BaseType.class.isAssignableFrom(type.getRawType())) {
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return newItemAdapter((TypeAdapter<BaseType>) delegate,
gson.getAdapter(new TypeToken<MyDTO>(){}));
} else {
return null;
}
}
private TypeAdapter newItemAdapter(
final TypeAdapter<BaseType> delagateAdapter,
final TypeAdapter<MyDTO> dtoAdapter) {
return new TypeAdapter<BaseType>() {
#Override
public void write(JsonWriter out, BaseType value) throws IOException {
delagateAdapter.write(out, value);
}
#Override
public BaseType read(JsonReader in) throws IOException {
MyDTO dto = dtoAdapter.read(in);
if("base".equals(dto.type)) {
return new BaseType(dto);
} else if ("type_a".equals(dto.type)) {
return new TypeA(dto);
} else {
return null;
}
}
};
}
}
and use like this --
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new BaseTypeAdapterFactory())
.create();
BaseType base = gson.fromJson(baseString, BaseType.class);

Unable to deserializer and serialize Java List<> with jackson

I am trying to deserializer and then serialize a java object.
I got an object like this one-
public class Blas{
private Integer blasRootId;
private List<Bla> blaList = new ArrayList<>();
public Blas() {}
/region g & s
getter and setters ..
//endregion
}
And the object -
public class Bla{
private String fileName;
private String description;
private Integer id;
public Bla() {}
//region g & s
getter and setters ..
//endregion
}
I deserialize the object with
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
String jsonString = mapper.writeValueAsString(Blas);
And the created json is like
{
"Blas": {
"blasRootId": 2840,
"blaList": [
"java.util.ArrayList",
[
{
"fileName": "RegularPayload",
"description": "",
"id": 2260
}
]
]
}
}
So when I try to serialize the created json the following error accord -
Can not instantiate value of type [simple type, class Bla] from String value ('java.util.ArrayList'); no single-String constructor/factory method
Who can i make the deserializer to write the list as it is ,without the addition "java.util.ArrayList" list, or how can i read it right?
Update :
It was my mistake, I added in the "mapper.configure" a parameter (That i don't recall which) that caused the serializer to add the "java.util.ArrayList".
My code example should work fine.
As prsmax asked, it depends how the code were you try to deserialize blas looks like, it seems like you are trying to take the string of blas and deserialize like this:
mapper.readValue(blasStr, Bla.class)
If you want only to deserialize a list of Bla you can do this:
JavaType javaType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, ValueType.class);
List<ValueType> list = mapper.readValue(str, javaType);
If you actually need the wrapper object Blas, then you should have a constructor marked with #JsonCreator accepting the a List<Bla> marked with #JsonProperty (There are other ways, but this is a fail safe way and makes the code readable)

Categories