Trouble creating POJO using GSON - java

I seem to be having trouble creating POJO(Plan Old Java Object) from JSON using GSON. I followed this tutorial to the T but I am still getting a null object. Here's my code:
JSONHandler.java
public class JSONHandler
{
private Gson gson;
private InputStream is;
private Reader reader;
private TripList tripList;
public JSONHandler(InputStream is)
{
this.is = is;
gson = new Gson();
reader = new InputStreamReader(is);
tripList = gson.fromJson(reader, TripList.class);
}
public Gson getGson() {
return gson;
}
public void setGson(Gson gson) {
this.gson = gson;
}
TripList.java
public class TripList
{
#SerializedName("Line")
public String line;
#SerializedName("CurrentTime")
public int currentTime;
public List<Train> Trips;
}
Train.java
public class Train
{
#SerializedName("TripID")
public String tripID;
#SerializedName("Destination")
public String dest;
public List<Prediction> Predictions;
}
Prediciton.java
public class Prediction
{
#SerializedName("StopID")
public int stopID;
#SerializedName("Stop")
public String stop;
#SerializedName("Seconds")
public int seconds;
}
blue.json
{
"TripList":
{
"CurrentTime":1342032950,
"Line":"Red",
"Trips": [
{
"TripID":"R982ECC1E",
"Destination":"Alewife",
"Predictions": [
{"StopID":"70094","Stop":"Ashmont","Seconds":370}
]
},
{
"TripID":"R982ECC78",
"Destination":"Ashmont",
"Note":"Big Red",
"Position":
{"Timestamp":1342032834,"Train":"1809","Lat":42.38725,"Long":-71.11894,"Heading":185},
"Predictions": [
{"StopID":"70067","Stop":"Harvard Square","Seconds":36},
{"StopID":"70069","Stop":"Central Square","Seconds":260}
]
}
]
}
}
The JSON format will follow this paradigm. GSON doesn't throw an error when cannot parse something correctly, it just returns a null value which is irritating. Is there something wrong with the format or the way I handled the java data objects? Any help would be much appreciated

I don't think JSON knows how to read a public List<Train> Trips; as the genericity is compile-time only.
see https://sites.google.com/site/gson/gson-user-guide#TOC-Collections-Examples for more details, but basically, i think you need to specify a TypeToken for you elements

When I ran into this issue we ran up against type erasure - you might want to look into implementing generics (if there will be more List elements in your code) or else explicitly telling Gson what type of type you want to serialize

Try initialising the Lists in each of your model classes.
I.e.
public class TripList
{
#SerializedName("Line")
public String line;
#SerializedName("CurrentTime")
public int currentTime;
public List<Train> Trips = new ArrayList<Train>();
}

There's a trivial problem: You're reading an object containing TripList:
{
"TripList": ...
}
but you tell Gson that you're reading a TripList itself:
tripList = gson.fromJson(reader, TripList.class);
As TripList contains no property named TripList, this does what it does: The unknown properties (i.e., "TripList") get ignored while the missing properties (i.e., all members of "TripList" get left at their default value.
So Gson behaves correctly here. I don't know if there's a problem with generics here. If so, the other answers may help.
Update
There's no other problem there, I've tried it. Just remove the enclosing thing.

Related

Gson: Derserialize a dynamic field

I'm using a very strange api, it's data field type is dynamic.
If error occured, the data field will be a string like this:
{
"code": 2001,
"data": "Error!"
}
And if success, the data field will be a object:
{
"code": 2000,
"data": {
"id": 1,
"name": "example"
}
}
I'm using the following Kotlin code to de-serialize it:
return Gson().fromJson(data, SimpleModel::class.java)
The model definition is down below:
import com.google.gson.annotations.SerializedName
class SimpleModel {
#SerializedName("code")
val code = 0
#SerializedName("data")
val data: SimpleData = SimpleData()
}
class SimpleData {
#SerializedName("id")
val id = ""
#SerializedName("name")
val name = ""
}
When no error occred, the code above works just fine. However when error occured, exception was thrown:
java.lang.IllegalStateException: Excepted BEGIN_OBJECT but was STRING at line 1 column x $path.data
Is there a way to de-serialize data field to an object or just anything and determine it's type by code manually?
You would need to write a custom deserializer, which decides what type to deserialize data into, depending on the runtime type of the node, and register that deserializer with your Gson instance. Unfortunately i am not familiar with kotlin syntax, so i can only give you pseudo code.
Field data in SimpleModel should be either Object, or make the class generic - SimpleModel<T>, and the field should be of type T as well.
Parse the input to gson's node type - JsonElement.
Get data field
JsonElement root = parseResponse();
root.getAsJsonObject().get("data").getAsString();
Use getAs...() methods to check type.
Get as string. If success, it's a string and set the string value in SimpleModel.
If you get exception getting as string, get it as object - getAsJsonObject(), parse the object to SimpleData and set this new object in SimpleModel.
You could use my my answer here as inspiration. Although it's about object mapper, it does the same thing - decides object type depending on the node type, and follows roughly the same algorithm i described above.
Also this guide has info about how to write yor own deserialzer and registering it.
Like the previous answer, I am not familiarized with Kotlin, and the following solution is in Java, but as I know it is easy to convert Java to Kotlin using IntelliJ built-in tools.
The success/error objects pair is a classic problem, and you can create your own way to solve it, but let's consider the following classes represent the success and error objects respectively (Java 17, pattern matching on switch enabled then):
abstract sealed class SimpleModel<T>
permits SimpleModelSuccess, SimpleModelError {
#SerializedName("code")
final int code;
SimpleModel(final int code) {
this.code = code;
}
}
final class SimpleModelSuccess<T>
extends SimpleModel<T> {
#SerializedName("data")
final T data;
private SimpleModelSuccess(final int code, final T data) {
super(code);
this.data = data;
}
}
final class SimpleModelError<T>
extends SimpleModel<T> {
#SerializedName("data") // the annotation is helping here!
final String message;
private SimpleModelError(final int code, final String message) {
super(code);
this.message = message;
}
}
The code above can explain itself. Now the core part that required more work than I thought before by providing you my first comment that appeared incomplete.
#RequiredArgsConstructor(access = AccessLevel.PRIVATE)
final class SimpleModelTypeAdapterFactory
implements TypeAdapterFactory {
#Getter
private static final TypeAdapterFactory instance = new SimpleModelTypeAdapterFactory();
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !SimpleModel.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// let's figure out what the model is parameterized with
final Type type = typeToken.getType();
final Type typeParameter;
if ( type instanceof ParameterizedType parameterizedType ) {
typeParameter = parameterizedType.getActualTypeArguments()[0];
} else {
throw new UnsupportedOperationException("Cannot infer type parameter from " + type);
}
// then borrow their respective type adapters for both success and error cases
#SuppressWarnings("unchecked")
final TypeAdapter<T> successDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelSuccess.class, typeParameter));
#SuppressWarnings("unchecked")
final TypeAdapter<T> errorDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelError.class, typeParameter));
return new TypeAdapter<>() {
#Override
public void write(final JsonWriter out, final T value) {
throw new UnsupportedOperationException();
}
#Override
public T read(final JsonReader in) {
// buffer the JSON tree first
// note that this solution may be very inefficient under some circumstances
final JsonObject buffer = Streams.parse(in).getAsJsonObject();
final JsonElement dataElement = buffer.get("data");
// is it's data is {...}, the consider it is success (by the way, what is code about?)
if ( dataElement.isJsonObject() ) {
return successDelegate.fromJsonTree(buffer);
}
// if it's a primitive, consider it's an error
if ( dataElement.isJsonPrimitive() ) {
return errorDelegate.fromJsonTree(buffer);
}
// well we've done our best...
throw new JsonParseException(String.format("Cannot deduce the model for %s", buffer.getClass()));
}
};
}
}
public final class SimpleModelTypeAdapterFactoryTest {
private static final class SomeJsonProvider
implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(final ExtensionContext context) {
return Stream.of(
Arguments.of(
"""
{
"code": 2000,
"data": {
"id": 1,
"name": "example"
}
}
"""
),
Arguments.of(
"""
{
"code": 2001,
"data": "Error!"
}
"""
)
);
}
}
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#ToString
private static final class SimpleData {
private final String id;
private final String name;
}
private static final Type simpleModelOfSimpleDataType = TypeToken.getParameterized(SimpleModel.class, SimpleData.class)
.getType();
#ParameterizedTest
#ArgumentsSource(SomeJsonProvider.class)
public void test(final String json) {
final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(SimpleModelTypeAdapterFactory.getInstance())
.create();
final SimpleModel<SimpleData> model = gson.fromJson(json, simpleModelOfSimpleDataType);
switch ( model ) {
case SimpleModelSuccess<SimpleData> success -> System.out.println(success.data);
case SimpleModelError<SimpleData> error -> System.out.println(error.message);
}
}
}
Here is what it prints to stdout:
SimpleModelTypeAdapterFactoryTest.SimpleData(id=1, name=example)
Error!
Well, yeah, this is a "bit" more tricky than it was suggested by my first comment.

How to serialize an object to a list of one elements with gson

I have some json string like this:
example1
{
"path":{
"start":"abc"
},
"name":"Fork1"
}
example2
{
"path":[{
"start":"abc"
},
{
"start":"def"
}],
"name":"Fork1"
}
and I want to serialize with one JAVA object like this:
#Data
public static class ForkNode {
private List<Path> path;
private String name;
}
#Data
public static class Path {
private String start;
}
new Gson().fromJson(jsonStr, ForkNode.class)
but it will throw an exception
IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 2 column 11 path $.path
So how do I treat the first example as a list of one elements?
Or is there any way I can serialize two different types of json strings with one object?
I don't think it is a good way to serialize two different types of json strings with ONE object.
For example 1, the Object should be like this:
#Data
public static class ForkNode {
// only one path
private Path path;
private String name;
}
#Data
public static class Path {
private String start;
}
new Gson().fromJson(jsonStr, ForkNode.class)
While For example 2, the Object should be like this:
#Data
public static class ForkNode {
// several paths
private List<Path> path;
private String name;
}
#Data
public static class Path {
private String start;
}
new Gson().fromJson(jsonStr, ForkNode.class)
In JSON:
Objects are enclosed directly in curly brackets {} While JSON
Arrays that are enclosed in square brackets [] inside JSON Objects.
One more thing, If you do really want to do that, I think you need to implement a custom deserializer by yourself. Please ref the doc of Gson.
I solved it by modify JsonObject.
I use this code to convent JsonObject to JsonArray, so I can deserializer it like JsonArray.
public void objectToArray(JsonObject jsonObject, String node) {
JsonElement jsonElement = jsonObject.get(node);
if (jsonElement instanceof JsonObject) {
JsonArray array = new JsonArray();
array.add(jsonElement);
jsonObject.remove(node);
jsonObject.add(node, array);
}
}

GSON - How can I parse two JSONArrays with the same name, but different parameters?

In the Reddit JSON API, comments can contain two different types of JSONArrays, both called "children".
"children" is usually an array of Objects containing a String "kind" and Object "data":
"children": [ { "kind": "t3", "data": {} } ...]
I've been handling these fine. My problem is that, sometimes, children will be a simple String array:
"children": [ "e78i3mq", "e78hees", "e78jq6q" ]
When parsing these, GSON throws an exception like the following:
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but
was STRING at line 1 column 3780 path
$[1].data.children[0].data.replies.data.children[0].data.replies.data.children[0].data.replies.data.children[0].data.children[0]
How can I handle these String array cases?
If the same endpoint is returning a different type in some instances I suggest wrapping that part in an object and using a deserializer to check the type and assign accordingly. You can do something like this:
public Parent serialize(String jsonString) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(WrappedChild.class, new ChildDeserializer());
Gson gson = builder.create();
return gson.fromJson(jsonString, Parent.class);
}
class Parent {
public List<WrappedChild> children;
}
class ObjectChild {
public String body;
}
class WrappedChild {
public ObjectChild objectChild;
public String stringChild;
}
class ChildDeserializer implements JsonDeserializer<WrappedChild> {
private Gson gson = new Gson();
#Override
public WrappedChild deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json != null) {
if (json.isJsonObject()) {
WrappedChild result = new WrappedChild();
result.objectChild = gson.fromJson(json, ObjectChild.class);
return result;
} else if (json.isJsonPrimitive()) {
WrappedChild result = new WrappedChild();
result.stringChild = json.getAsString();
return result;
}
}
return null; // Or throw new Exception("Unknown child type");
}
}
If you are using retrofit just pass the Gson created by the builder to GsonConverterFactory.create as a parameter when creating your service.
You should carefully study answer from Emre Eran because that way you will have a total control of deserializing. I will just give an another approach which in some cases might require less effort. It bases on the "basic intelligence" of Gson deserializing.
If you declare your class that contains children like:
public class Parent {
Collection<?> children;
}
Gson tries its best to "guess" the object type. If it faces a simple string it will be deserialized to a String. If it faces data like in your 1st Json example, it will deserialized to com.google.gson.internal.LinkedTreeMap which is just a Java version of Json tree.
So depending on how complex is the data object in your 1st example and how you use the result overall you might not need to write custom deserializers (which anyway might be better solution eventually).
Sorry for the late answer, thanks for leading me in the right direction Emre!
I ended up getting GsonBuilder to work with a custom method, getGsonAdaptedData.
After retrieving the JSON response in a background thread:
...
Gson gson = new GsonBuilder().registerTypeAdapter(Data.class, (JsonDeserializer<Data>) (arg0, arg1, arg2) -> {
JsonObject dataJsonObject = arg0.getAsJsonObject();
Data data = new Gson().fromJson(dataJsonObject, Data.class);
return RedditUtils.getGsonAdaptedData(dataJsonObject.get("children").getAsJsonArray(), data);
}).create();
final Feed responseSubredditFeed = gson.fromJson(jsonString, Feed.class);
...
RedditUtils.getGsonAdaptedData
// JSON returned for Reddit comments can contain two types of arrays named "children"
// This method checks to see if we were given a Children array or String array
// JSON member "replies" is similar, and can be found in the Data of some Children
// If the method finds a nested "children" array, it recursively adapts its Data
public static Data getGsonAdaptedData(JsonArray childrenJsonArray, Data data) {
if (childrenJsonArray.size() > 0) {
Gson gson = new Gson();
if (childrenJsonArray.get(0).isJsonObject()) {
data.setChildrenList(gson.fromJson(childrenJsonArray,
new TypeToken<List<Children>>() {
}.getType()));
// Loops through every Data object in the array looking for children and replies
for (int i = 0; i < childrenJsonArray.size(); i++) {
JsonObject nestedDataJsonObject = childrenJsonArray.get(i).getAsJsonObject().get("data").getAsJsonObject();
if (nestedDataJsonObject.has("children")) {
getGsonAdaptedData(nestedDataJsonObject.get("children").getAsJsonArray(), data.getChildren().get(i).getData());
} else if (nestedDataJsonObject.has("replies") && nestedDataJsonObject.get("replies").isJsonObject()) {
data.getChildren().get(i).getData().setRepliesObject(gson.fromJson(nestedDataJsonObject.get("replies"),
new TypeToken<Replies>() {
}.getType()));
getGsonAdaptedData(nestedDataJsonObject.get("replies").getAsJsonObject().get("data").getAsJsonObject().get("children").getAsJsonArray(), data.getChildren().get(i).getData());
}
}
} else {
data.setRepliesList(gson.fromJson(childrenJsonArray,
new TypeToken<List<String>>() {
}.getType()));
}
}
return data;
}

JSON array in GSON parses, but objects are empty

i am attempting to turn a json string into objects with gson.
I have a very simple example below, and it runs, but the resulting answer is empty, ie: my Answer objects's text field is empty.
import com.google.gson.*;
public class Meow {
public static void main(String[] args) throws Exception{
Gson gson = new Gson();
String jsonOutput = "[{\"answer\":{\"text\":\"text1\"}},{\"answer\":{\"text\":\"text2\"}} ]";
Answer[] a = gson.fromJson(jsonOutput, Answer[].class);
for(Answer i:a) {
System.out.println(i.text);
}
}
public class Answer {
public String text;
public Answer(String text) {
super();
this.text=text;
}
public String toString(){
return text;
}
public void setText(String a){
this.text=a;
}
}
}
Because your JSON doesn't match your class.
Your JSON right now is an array of objects, each containing an answer object as a field.
Your JSON the way you have things would need to look like:
String jsonOutput = "[{\"text\":\"text1\"},{\"text\":\"text2\"}]";
Edit to add from comments:
If you can't change the output, you need a "wrapper". Something like:
public class AnswerWrapper {
public Answer answer;
// etc
}
And use an array of those. That is what the JSON will map to. It can't see them as Answer objects because ... they're not.
One More Edit to Add: Your other option is to write custom deserializers for your classes. I'm a bit mixed on whether you should do this or not, but it will work. The reason I say that is that you have JSON that isn't an array of Answer objects, but you want it to be. I think I'd be annoyed if I came across this in production code because without understanding what was going on it could be confusing.
With that caveat being said, you can create a custom JsonDeserializer and use GsonBuilder:
class AnswerDeserializer implements JsonDeserializer<Answer> {
public Answer deserialize(JsonElement je, Type type,
JsonDeserializationContext jdc)
throws JsonParseException {
return new Answer(je.getAsJsonObject().get("answer")
.getAsJsonObject().get("text").getAsString());
}
}
Then your code would look like:
public static void main(String[] args) throws Exception{
String jsonOutput = "[{\"answer\":{\"text\":\"text1\"}},{\"answer\":{\"text\":\"text2\"}} ]";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Answer.class, new AnswerDeserializer());
Gson gson = gsonBuilder.create();
Answer[] a = gson.fromJson(jsonOutput, Answer[].class);
for(Answer i:a) {
System.out.println(i.text);
}
}
If it were me, and I had JSON that wasn't what I needed it to be but wanted to use GSON to directly serialize/deserialize I'd create the Answer class as a wrapper that hid the details:
/**
* Due to how our JSON is being provided we created an inner
* class.
**/
public class Answer {
private RealAnswer answer;
private class RealAnswer {
public String text;
}
...
}
With the public getters/setters for Answer accessing the private RealAnswer. It just seems way cleaner and easier to understand for the next guy.

How to keep fields sequence in Gson serialization

Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?
public class Foo {
public String bar;
public String baz;
public Foo( String bar, String baz ) {
this.bar = bar;
this.baz = baz;
}
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));
The string jsonRequest can be:
{ "bar":"bar", "baz":"baz" } (correct)
{ "baz":"baz", "bar":"bar" } (wrong sequence)
You'd need to create a custom JSON serializer.
E.g.
public class FooJsonSerializer implements JsonSerializer<Foo> {
#Override
public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("bar", context.serialize(foo.getBar());
object.add("baz", context.serialize(foo.getBaz());
// ...
return object;
}
}
and use it as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...
This maintains the order as you've specified in the serializer.
See also:
Gson User Guide - Custom serializers and deserializers
If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with #JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.
And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.
Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.
I had the same problem and it was solved by literal order of properties' names in the class.
The example in the question will always return the following jsonRequest:
{ "bar":"bar", "baz":"baz" }
In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:
public class Foo {
public String f1_baz;
public String f2_bar;
public Foo ( String f1_baz, String f2_bar ) {
this.f1_baz = f1_baz;
this.f2_bar = f2_bar;
}
}
jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }
Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:
private void standardizeFormat(File dir) throws IOException {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
String path = child.getPath();
JsonReader jsonReader = new JsonReader(new FileReader(path));
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
Object data = gson.fromJson(jsonReader, Object.class);
JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
jsonWriter.setIndent(" ");
gson.toJson(data, Object.class, jsonWriter);
jsonWriter.close();
}
}
}
private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
#Override
public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
TreeSet sorted = Sets.newTreeSet(foo.keySet());
for (Object key : sorted) {
object.add((String) key, context.serialize(foo.get(key)));
}
return object;
}
}
It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...

Categories