Does Gson ignore #JsonAdapter on JsonElement fields? - java

I have an object to serialize using Gson:
class User {
String firstname;
String lastname;
JsonElement opaqueData;
}
On the top object level, I want Gson to ignore null fields (say, if lastname is null, it should not be serialized).
But in field opaqueData , I want nulls to be serialized since it is supposed to a block of opaque data. So using new GsonBuilder().serializeNulls().create().toJson(user) would not work.
But #JsonAdapter does not work either
class User {
String firstname;
String lastname;
#JsonAdapter(NullAdapter.class)
JsonElement opaqueData;
}
class NullAdapter extends TypeAdapter<JsonElement> {
#Override
public void write(JsonWriter out, JsonElement value) throws IOException {
out.jsonValue(new GsonBuilder().serializeNulls().create().toJson(value));
}
#Override
public JsonElement read(JsonReader in) throws IOException {
// TODO Auto-generated method stub
return null;
}
}
It appears that the adapter is ignored by Gson.
Is this expected behavior? How can make this happen without create an adapter for the whole User class then manually serialize each field?

Gson does not bother to adapt its internal data type JsonElement. I do not know if there is any point in since JsonElement kind is the adapted result.
Having a glance at the source code of Gson you can see there two top level methods:
For any object
public String toJson(Object src) {
if (src == null) {
return toJson(JsonNull.INSTANCE);
}
return toJson(src, src.getClass());
}
I think it is obvious without copying the rest of the code that above method one has searching & setting of adapters (and querying adapter factories) in its execution path. But then there is also method like:
For JsonElement
public String toJson(JsonElement jsonElement) {
StringWriter writer = new StringWriter();
toJson(jsonElement, writer);
return writer.toString();
}
and for that execution path there is no searching nor setting any adapters as JsonElement is not meant to be adapted anymore.
So when Gson hits any JsonElement it just serializes ti as formatted string and you cannot affect on that wiht any adapter.
If you really need to serialize something like that maybe you could make a wrapper class for the JsonElement, something like:
public class JsonElementWrapper {
public JsonElement jsonElement;
}
and adjust your TypeAdapter to adapt that to `null``or actual value.

Related

Removing Custom Serialisation for JSON objects

I currently have a custom serialiser. Purpose of my custom serialiser is to format my JSON object from this: {"id":["21","22", 23"]} to this {"id":"21,22,23"}.
My current implementation of my custom serialiser:
public static class ListSerialiser implements JsonSerializer<List<Member>> {
#Override
public JsonElement serialize(List<Member> src, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
List<String> memberId = new ArrayList<>(src.size());
for (Member member : src) {
memberId.add("" + Member.getId());
}
String memberIdAsString = TextUtils.join(",", memberId);
object.addProperty("[%s]", memberIdAsString);
return object;
}
}
Although my implementation got me what I wanted, just out of curiosity, I was wondering if there's a way to serialise without having to use Text Utils or string formatters to achieve this outcome: {"id":"21,22,23"}
There are a lot of ways to join strings, you can see most of them here
My favourite is:
String memberIdAsString = memberId.stream().collect(Collectors.joining(","));

How do I treat empty Strings as null objects with GSON?

I'm retrieving comments from the Reddit API. The model is threaded such that each Comment can internally have a List of Comments, named replies. Here's an example of how a JSON response would look:
[
{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"comment",
"replies":{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"reply to comment",
"replies":""
}
}
]
}
}
}
}
]
}
}
]
Here is how I model this with POJOs. The response above would be considered a List of CommentListings.
public class CommentListing {
#SerializedName("data")
private CommentListingData data;
}
public final class CommentListingData {
#SerializedName("children")
private List<Comment> comments;
}
public class Comment {
#SerializedName("data")
private CommentData data;
}
public class CommentData {
#SerializedName("body")
private String body;
#SerializedName("replies")
private CommentListing replies;
}
Note how the bottom level CommentData POJO refers to another CommentListing called "replies".
This model works until GSON reaches the last child CommentData where there are no replies. Rather than providing a null, the API is providing an empty String. Naturally, this causes a GSON exception where it expects an object but finds a String:
"replies":""
Expected BEGIN_OBJECT but was STRING
I attempted to create a custom deserializer on the CommentData class, but due to the recursive nature of the model it seems not to reach the bottom levels of the model. I imagine this is because I'm using a separate GSON instance to complete deserialization.
#Singleton
#Provides
Gson provideGson() {
Gson gson = new Gson();
return new GsonBuilder()
.registerTypeAdapter(CommentData.class, new JsonDeserializer<CommentData>() {
#Override
public CommentData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject commentDataJsonObj = json.getAsJsonObject();
JsonElement repliesJsonObj = commentDataJsonObj.get("replies");
if (repliesJsonObj != null && repliesJsonObj.isJsonPrimitive()) {
commentDataJsonObj.remove("replies");
}
return gson.fromJson(commentDataJsonObj, CommentData.class);
}
})
.serializeNulls()
.create();
}
How can I force GSON to return a null instead of a String so that it doesn't try to force a String into my POJO? Or if that's not possible, manually reconcile the data issue? Please let me know if you need additional context or information. Thanks.
In general your code looks good, but I would recommend a few things:
Your type adapters should not capture Gson instances from outside. Type adapter factories (TypeAdapterFactory) are designed for this purpose. Also, in JSON serializers and deserializers you can implicitly refer it through JsonSerializationContext and JsonDeserializationContext respectively (this avoids infinite recursion in some cases).
Avoid modification JSON objects in memory as much as possible: serializers and deserializers are just a sort of pipes and should not bring you surprises with modified objects.
You can implement a generic "empty string as a null" type deserializer and annotate each "bad" field that requires this kind of deserialization strategy. You might consider it's tedious, but it gives you total control wherever you need it (I don't know if Reddit API has some more quirks like this).
public final class EmptyStringAsNullTypeAdapter<T>
implements JsonDeserializer<T> {
// Let Gson instantiate it itself
private EmptyStringAsNullTypeAdapter() {
}
#Override
public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
if ( jsonElement.isJsonPrimitive() ) {
final JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
if ( jsonPrimitive.isString() && jsonPrimitive.getAsString().isEmpty() ) {
return null;
}
}
return context.deserialize(jsonElement, type);
}
}
And then just annotate the replies field:
#SerializedName("replies")
#JsonAdapter(EmptyStringAsNullTypeAdapter.class)
private CommentListing replies;

exclude NULL from JsonArray while de-serializing json

I receive an invalid json (extra comma in the end of the list) in request body, which is being successfully deserialized by GSON library. On inspection I see that GSON is inserting a NULL object in the end.
{
"content": "Test 2",
"timestamp": 1494311947530,
"entities": [
{"name": "entity1"},
{"name": "entity2"},
{"name": "entity3"},
{"name": "entity4"},
{"name": "entity5"},
]
}
Is there a way by which I can either instruct GSON not to accept invalid json or remove NULL objects from JsonArray.
I have tried registering type adapter for Set.class but I can't proceed further with this solution as it is not possible to get Type of the parameterized object.
public class RemoveNullCollectionSerializer<T> implements JsonDeserializer<Set<T>> {
#Override
public Set<T> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonArray elements = jsonElement.getAsJsonArray();
Set<T> result = new HashSet();
for (JsonElement element : elements) {
if (element.isJsonNull()) continue;
T value = (T) context.deserialize(element, Object.class);
result.add(value);
}
return result;
}
}
I'm trying not to register custom adapters as there are a lot of models in the project and each will require one adapter, which will be a big task.
I'm sorry but Gson seems not be able to do this. There's a special mode in Gson instructing it to work in "lenient" mode -- this is why you're getting a null element in the result collection. The lenient mode tells Gson ignore some soft invalid JSON issues, one of them is reading trailing array elements and object properties. If you take a look at CollectionTypeAdapterFactory that actually is responsible for collections read, you see:
#Override public Collection<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Collection<E> collection = constructor.construct();
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
in.endArray();
return collection;
}
As you can see, an element instance is read and always added to the result collection. The elementTypeAdapter.read may fail in non-lenient mode, but this means that the object will not be constructed totally. You can check it like this:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Collection.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateAdapter.write(out, value);
}
#Override
public T read(final JsonReader in)
throws IOException {
final boolean wasLenient = in.isLenient();
try {
in.setLenient(false);
return delegateAdapter.read(in);
} finally {
in.setLenient(wasLenient);
}
}
};
}
})
.create();
Note that the read method disables the lenient mode temporarily and then restores it back. The code above would cause
Use JsonReader.setLenient(true) to accept malformed JSON at line 20 column 3 path $.entities[5]
for you trailing "emptiness" JSON document. There is no way to check if the next JSON token would fail or not according to the lenient mode in Gson. Not the best per se (but probably the only way) is using reflection in order to get the JsonReader backing reader (Reader actually) and decorating it with a new JsonReader overriding hasNext() method or something like that. It would be nice if JsonReader would support a method to check if its last value was generated because of the lenient mode set to true. But even if it supported something like that, there would be no guarantee to remove the last element because a particular type adapter might return a non-modifiable collection.
By the way, I think this issue should be posted to https://github.com/google/gson/issues/ -- I'd love to get the Gson team feedback.

Using Gson to elegantly handle nested json objects?

I'm using Gson to parse responses from a server on Android. Each response has some useless (to me) data on it that complicates my Gson models. Here is the general hierarchy of json returned:
response: {
date: 1406253006807,
otherUselessData1: "This is some useless data",
otherUselessData2: "This is some useless data",
usefulJsonObject: { <---- This is really the object that I care about
}
}
Everything above or at the same level as usefulJsonObject I could really do without. The useless data is returned for every request, and the actual response is embedded beneath as the usefulJsonObject. This wouldn't be a big problem but it's really cluttering up my gson model objects.
For example:
Let's say I have 3 requests I can make: A, B, and C. For each response it seems I need to make a minimum of 3 custom classes.
public class ResponseA {
#SerializedName("response") ResponseObjectA responseObject;
public static class ResponseObjectA {
#SerializedName("usefulJsonObject") UsefulObjectA usefulObject;
}
public static class UsefulObjectA {
}
}
I've tried a few solutions, but I haven't found anything elegant that wouldn't add an extra step to my process. I'm using retrofit to do my http requests and it's really nice that it just returns the fully parsed gson object to me. I've thought of other solutions like having the useful object just be a JsonElement and then doing a 2nd gson call after the first comes back. Again, not ideal.
I just wanted to know if I was missing something. Surely I'm not the only one who's encountered something like this, and so I thought I'd ask how other people would handle something like this.
It is initialization Instance value, not NULL value. Check my example.
Address.java
public class Address {
public Address(){
}
}
Person.java
public class Person {
private String name;
private String nrc;
private Address address;
public Person(String name, String nrc, Address address) {
this.name = name;
this.nrc = nrc;
this.address = address;
}
}
The following Json string is equalvent to
Person person = new Person("Zaw Than Oo", "11111", null);
{
"name": "Zaw Than Oo",
"nrc": "11111"
}
The following Json string is equalvent to
Person person = new Person("Zaw Than Oo", "11111", new Address());
{
"name": "Zaw Than Oo",
"nrc": "11111",
"address": {} <-- here use less object for you.
}
Even if you don't create new Instance, Other lib/api(you used) may be create that instance by Reflection.
Short to the Point
{
...
"xxx": {} --> new instance without data/value
...
}
{
...
--> null value
...
}
I never found an elegant way dealing with just Gson. I tried several options with Generics, all of which didn't work or left something to be desired.
Since I'm using Retrofit, I decided to override the GsonConverter, and just filter out the unnecessary information from all my requests. It ends up not being as flexible, as in I can't use the same Retrofit network interface for calls to other servers, but I'm not really doing that, and it also has the down side of having 2 rounds of json parsing calls (meh). You could probably do this more efficiently, but this is working for me for now.
public class CustomGsonConverter extends GsonConverter {
private Gson mGson;
public CustomGsonConverter(Gson gson) {
super(gson);
this.mGson = gson;
}
public CustomGsonConverter(Gson gson, String encoding) {
super(gson, encoding);
this.mGson = gson;
}
#Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
try {
CustomResponse customResponse = mGson.fromJson(new InputStreamReader(body.in()), CustomResponse.class);
return mGson.fromJson(customResponse.responseObject.data, type);
} catch (IOException e) {
throw new ConversionException(e);
}
}
public static class CustomResponse {
#SerializedName("rsp") ResponseObject responseObject;
public static class ResponseObject {
// #SerializedName("date") long date;
#SerializedName("data") JsonElement data;
}
}
}
Maybe there is a better way that I'm just not realizing.

Object constructor from json or xml string

I'm working on a service that spits json everywhere. However one of the providers I consume uses XML as serialization format so I want to be able to use the same interface for parsing (and spitting out) JSON with that XML.
Problem is, I don't know of a XML type, or object that would allow me to override my constructor easily.
Hoping to clarify my point, here's some code:
public class JsonData {
private Hashtable<String, Variant> map = new Hashtable<String, Variant>();
public JsonData() {
}
public JsonData(String jsonString) {
this.deserialize(jsonString);
}
Ideally I would like a third constructor to do something like:
public JsonData(XMLString jsonString) {
this.xmldeserialize(jsonString);
}
Note how both relevant constructors take a plain string as argument.
Any pointer?
You can use static methods to create object from json or xml strings:
public static JsonData fromJson(String json) {
JsonData data = new JsonData();
data.deserializeJson(json);
return data;
}
public static JsonData fromXml(String xml) {
JsonData data = new JsonData();
data.deserializeXml(xml);
return data;
}
Can't you just check if the input is json or xml (using regex) and call appropriate method to deserialize.
Like
public JsonData(String jsonString) {
if(isValidJson(jsonString){
this.deserialize(jsonString);
} else {
this.xmldeserialize(jsonString);
}
}

Categories