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.
Related
I receive different objects set from the API. Each response have a follow structure:
items:[
{
user_id:1,
tags: {..}
},
{..}
]
The problem is that I do not want so unuseful and not readable structure.
I mean, all my methods (I use Retrofit library) must have some next signature:
Call<UserRepresantation>...
Call<RepoRepresentation>...
instead
Call<List<Users>>
Call<List<Repos>>
And also I have to use additional entities every time:
class UserRepresentation{
List<Users> items;
}
The Retrofite has possibility to use different converters for the serialization, for example:
Retrofit.Builder()
.baseUrl(stckUrl)
.addConverterFactory(GsonConverterFactory.create(new Gson())) < --- converter applying
.build();
As I understand I can use JsonSeializer to configure such behavior, but I can't figure out in which way. Can anyone help me to solve this issue?
So, in the simple words:
we have a response:
items:[
{
user_id:1,
tags: {..}
},
{..}
]
And we need to receive:
List<Users> = gson.fromJson(respose, User.class);
One solution would be to write a TypeAdapterFactory which performs the unwrapping when asked to deserialize any List<User> and List<Repo>, or in general for any List. However, the problem with this is that it would also apply to any nested lists of these types, for example when your User class has a field List<Repo> repos then that adapter factory would also try to unwrap its value, and fail.
So a more reliable solution might be to implement a TypeAdapterFactory which keeps track of whether it is currently being used to deserialize the top-level value and in that case unwrap / flatten the data. If not used for the top-level value it could simply let the other registered adapter factories handle the data:
class FlatteningTypeAdapterFactory implements TypeAdapterFactory {
public static final FlatteningTypeAdapterFactory INSTANCE = new FlatteningTypeAdapterFactory();
private FlatteningTypeAdapterFactory() { }
/** Tracks whether this is a nested call to this factory */
private static final ThreadLocal<Boolean> isNestedCall = new ThreadLocal<>();
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only handle top-level value, for nested calls let other factory handle it
// Uses Boolean.TRUE.equals to handle case where value is `null`
if (Boolean.TRUE.equals(isNestedCall.get())) {
return null;
}
TypeAdapter<T> delegate;
isNestedCall.set(true);
try {
delegate = gson.getDelegateAdapter(this, type);
} finally {
isNestedCall.remove();
}
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) {
throw new UnsupportedOperationException();
}
#Override
public T read(JsonReader in) throws IOException {
in.beginObject();
String name = in.nextName();
if (!name.equals("items")) {
throw new IllegalArgumentException("Unexpected member name: " + name);
}
T value;
// While using delegate adapter also set isNestedCall in case delegate looks up
// another adapter dynamically while its `read` method is called
isNestedCall.set(true);
try {
value = delegate.read(in);
} finally {
isNestedCall.remove();
}
in.endObject();
return value;
}
};
}
}
You would then have to register it with a GsonBuilder before constructing the GsonConverterFactory:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(FlatteningTypeAdapterFactory.INSTANCE)
.create();
Note that the code above has not been extensively tested; there might be bugs or corner cases where it does not work correctly.
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.
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;
I'm getting this issue, I do not want to solve that issue but find to way to say to GSON "skip errors and continue" parsing :
Can't parses json : java.lang.IllegalStateException:
Expected a string but was BEGIN_OBJECT at line 1 column 16412
Code used:
JsonReader reader = new JsonReader(new StringReader(data));
reader.setLenient(true);
Articles articles = gson.create().fromJson(reader, Articles.class);
The data are (to simplify) : Articles->Pages->medias.fields. One field in the current error is defined as string but I'm receiving an object (but again it is only one occurrence). I cannot add protection everywhere so my question is: "is there a skip and continues in GSON ?
I want to avoid JsonSysntaxException with GSON when there is an issue on a node and I expect at least to retrieve the partial data parsed. In my case I would have had 99.999% of the data and only my wrong field as null… I know it seems not clean, but I would enable the "strict mode" for unit test or continous integration to detect problem and on production I would enable a "soft mode" so my application could start (even when the server side doing errors). I cannot say to my custom, your app cannot start because an article has an invalide data.
Is GSON have a "skip and continue on error" ?
Here is the solution:
You have to create TypeAdapterFactory which will allow you to intercept default TypeAdapter,
but still leave to you access to default TypeAdapter as a delegate.
Then you can just try to read value using delegate from default TypeAdapter and process unexpected data as you wish.
public Gson getGson() {
return new GsonBuilder()
.registerTypeAdapterFactory(new LenientTypeAdapterFactory())
.create();
}
class LenientTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
try { //Here is the magic
//Try to read value using default TypeAdapter
return delegate.read(in);
} catch (JsonSyntaxException e) {
//If we can't in case when we expecting to have an object but array is received (or some other unexpected stuff), we just skip this value in reader and return null
in.skipValue();
return null;
}
}
};
}
}
I think that the answer is simply: no, generally it can't.
Due to the recursive parsing nature of the library if something is wrong, it would throw some kind of exception. It would interesting if you could post a SSCCE of your problem to experiment if it is possible to create a customized type adapter that handle better exceptions.
I am using google gson-2.2.1 library for parsing large response of JSON.
I have to parse a JSON response where structure may vary.
First case, when the response contains more than one team:
"Details":{
"Role":"abc",
"Team":[
{
"active":"yes",
"primary":"yes",
"content":"abc"
},
{
"active":"yes",
"primary":"yes",
"content":"xyz"
}
],
Second case, when only one team is passed:
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
}
}
There are my base classes used for parsing:
class Details {
public String Role;
public ArrayList<PlayerTeams> Team = new ArrayList<PlayerTeams>();
PlayerTeams Team; // when JsonObject
}
class PlayerTeams {
public String active;
public String primary;
public String content;
}
The problem is that I can not use ArrayList<PlayerTeams> when I have only one of them and it's returned as JsonObject.
Gson can identify static format of JSON response. I can trace full response dynamically by checking if "Team" key is instance of JsonArray or JsonObject but it would be great if a better solution is available for that.
Edit :
If my response is more dynamic..
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
"Test":
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
}
}
In my edited question, I am facing problem while my response is more dynamic..Team and Test can be JsonArray or JsonObject.. It really harassing me because sometime Test object may array when more data, may object when single data, string when no data. There is no consistency in response.
You need a type adapter. This adapter would be able to distinguish which format is coming and instance the right object with the right values.
You can do this by:
implement your own type adapter by creating a class that implements JsonSerializer<List<Team>>, JsonDeserializer<List<Team>>, of course JsonSerializer is just needed in case you need to serialize it in that matter too.
Register the type adapter to you GsonBuilder like: new GsonBuilder().registerTypeAdapter(new TypeToken<List<Team>>() {}.getType(), new CoupleAdapter()).create()
The deserialize method could look like:
public List<Team> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws com.google.gson.JsonParseException {
if (json.isJsonObject()) {
return Collections.singleton(context.deserialize(json, Team.class));
} else {
return context.deserialize(json, typeOfT);
}
}