I have two JSON objects with similar fields. The only difference is that the first one always has field
type: "type1"
And second can have anything in 'type' field except 'type1'.
I want them to be parsed into java objects with different classes (using classes FirstType.class and OtherType.class). Is it possible?
Object one:
{
id: "1j23jr8swgs8"
type: "type1"
}
Object two:
{
id: "3sdaa3dq18"
type: "unknown_type"
}
And java classes:
class FirstType
{
String id;
}
class OtherType
{
String id;
}
Google gson will work nicely here.
You can do something like this:-
class ObjectName {
String id;
String type;
}
Gson gson = new GsonBuilder().serializeNulls().create();
ObjectName name = gson.fromJson(json, ObjectName.class);
FirstType firstType = null;
SecondType secondType = null;
if(name.type.equals("type1"))
firstType = new FirstType(name.id);
else
secondType = new SecondType(name.id);
It's been a heck of a long time since I used Gson, but it should look something like this:
//convert your json string into a json object
JsonElement element = new JsonParser().parse(jsonString);
JsonObject object = element.getAsJsonObject();
//get the relevant value from your object
String result = object.get("type").toString();
//compare and convert accordingly
if (result.equals("type1")) {
ObjectOne object = gson.fromJson(element, ObjectOne.class);
} else {
ObjectTwo object = gson.fromJson(element, ObjectTwo.class);
}
Try this and see if it works!
Related
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;
}
I try to explain my situation as best as possible. I want to read a JSON file with Gson which contains a list of objects. This object is the Base class as I have several objects which extend it.
{
"name":"Test",
"url":"http://test.test",
"script":[
{ "actionId":1, "actionType":"click", "id":"testId", "redirect" : "http://google.de" },
{ "actionId":2, "actionType":"write", "id":"testId2", "content":"testContent" }
]
}
The base class is called ScriptElement, and it contains some values every child object needs too. ScriptInput and ScriptClickable are the children.
If I convert my list to JSON, I get a well generated JSON file. But when I read the JSON file and convert it back to the object, it only contains ScriptElement objects and is missing the additional information of ScriptInput and ScriptClickable.
This is a classic case: it happens because Gson does not have any clue on how ScriptElement and actionType values are related to each other. You have just to tell Gson how these could be mapped.
For simplicity, I'm assuming your mappings are something like these:
final class Response {
final String name = null;
final URL url = null;
final List<ScriptElement> script = null;
}
abstract class ScriptElement {
final int actionId = Integer.valueOf(0);
final String id = null;
}
final class ScriptClickable extends ScriptElement {
final URL redirect = null;
}
final class ScriptInput extends ScriptElement {
final String content = null;
}
Now, just register a JsonDeserializer to produce a ScriptElement depending on the actionType property value.
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(ScriptElement.class, (JsonDeserializer<ScriptElement>) (jsonElement, type, context) -> {
final String actionType = jsonElement.getAsJsonObject().getAsJsonPrimitive("actionType").getAsString();
switch ( actionType ) {
case "click":
return context.deserialize(jsonElement, ScriptClickable.class);
case "write":
return context.deserialize(jsonElement, ScriptInput.class);
default:
throw new JsonParseException("Unrecognized action type: " + actionType);
}
})
.create();
As you can see, the only thing you have to do is:
Get the action type from the JSON tree.
Dispatch the action type value to the downstream deserialization context with a proper class reference (let Gson just do it what it's the best at).
Once you have done it, you can test it:
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43843846.class, "test.json") ) {
final Response response = gson.fromJson(jsonReader, Response.class);
for ( final ScriptElement element : response.script ) {
if ( element instanceof ScriptClickable ) {
final ScriptClickable clickable = (ScriptClickable) element;
System.out.println(clickable.redirect);
} else if ( element instanceof ScriptInput ) {
final ScriptInput input = (ScriptInput) element;
System.out.println(input.content);
} else {
throw new AssertionError(element);
}
}
}
Output:
http://google.de
testContent
See more on polymorphic objects deserialization:
How to determine class of object from GSON parse?
How to parse dynamic json in android with retrofit 2 using annotations
Okay, well based on the json structure make a class with these attributes
public class User{
private String name;
private String url;
private ArrayList<Script>script;
}
public class Script{
private int actionId;
private String actionType;
private String id;
private String redirect;
}
Now when you want to parse it by gson
You should create a reference from user class
pull the json from the file to a myJsonString variable which will you create
User user=new Gson().fromJson(myJsonString,User.class);
I'm trying to parse some JSON data using gson in Java that has the following structure but by looking at examples online, I cannot find anything that does the job.
Would anyone be able to assist?
{
"data":{
"id":[
{
"stuff":{
},
"values":[
[
123,
456
],
[
123,
456
],
[
123,
456
],
],
"otherStuff":"blah"
}
]
}
}
You just need to create a Java class structure that represents the data in your JSON. In order to do that, I suggest you to copy your JSON into this online JSON Viewer and you'll see the structure of your JSON much clearer...
Basically you need these classes (pseudo-code):
class Response
Data data
class Data
List<ID> id
class ID
Stuff stuff
List<List<Integer>> values
String otherStuff
Note that attribute names in your classes must match the names of your JSON fields! You may add more attributes and classes according to your actual JSON structure... Also note that you need getters and setters for all your attributes!
Finally, you just need to parse the JSON into your Java class structure with:
Gson gson = new Gson();
Response response = gson.fromJson(yourJsonString, Response.class);
And that's it! Now you can access all your data within the response object using the getters and setters...
For example, in order to access the first value 456, you'll need to do:
int value = response.getData().getId().get(0).getValues().get(0).get(1);
Depending on what you are trying to do. You could just setup a POJO heirarchy that matches your json as seen here (Preferred method). Or, you could provide a custom deserializer. I only dealt with the id data as I assumed it was the tricky implementation in question. Just step through the json using the gson types, and build up the data you are trying to represent. The Data and Id classes are just pojos composed of and reflecting the properties in the original json string.
public class MyDeserializer implements JsonDeserializer<Data>
{
#Override
public Data deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
final Gson gson = new Gson();
final JsonObject obj = je.getAsJsonObject(); //our original full json string
final JsonElement dataElement = obj.get("data");
final JsonElement idElement = dataElement.getAsJsonObject().get("id");
final JsonArray idArray = idElement.getAsJsonArray();
final List<Id> parsedData = new ArrayList<>();
for (Object object : idArray)
{
final JsonObject jsonObject = (JsonObject) object;
//can pass this into constructor of Id or through a setter
final JsonObject stuff = jsonObject.get("stuff").getAsJsonObject();
final JsonArray valuesArray = jsonObject.getAsJsonArray("values");
final Id id = new Id();
for (Object value : valuesArray)
{
final JsonArray nestedArray = (JsonArray)value;
final Integer[] nest = gson.fromJson(nestedArray, Integer[].class);
id.addNestedValues(nest);
}
parsedData.add(id);
}
return new Data(parsedData);
}
}
Test:
#Test
public void testMethod1()
{
final String values = "[[123, 456], [987, 654]]";
final String id = "[ {stuff: { }, values: " + values + ", otherstuff: 'stuff2' }]";
final String jsonString = "{data: {id:" + id + "}}";
System.out.println(jsonString);
final Gson gson = new GsonBuilder().registerTypeAdapter(Data.class, new MyDeserializer()).create();
System.out.println(gson.fromJson(jsonString, Data.class));
}
Result:
Data{ids=[Id {nestedList=[[123, 456], [987, 654]]}]}
POJO:
public class Data
{
private List<Id> ids;
public Data(List<Id> ids)
{
this.ids = ids;
}
#Override
public String toString()
{
return "Data{" + "ids=" + ids + '}';
}
}
public class Id
{
private List<Integer[]> nestedList;
public Id()
{
nestedList = new ArrayList<>();
}
public void addNestedValues(final Integer[] values)
{
nestedList.add(values);
}
#Override
public String toString()
{
final List<String> formattedOutput = new ArrayList();
for (Integer[] integers : nestedList)
{
formattedOutput.add(Arrays.asList(integers).toString());
}
return "Id {" + "nestedList=" + formattedOutput + '}';
}
}
I am getting this data from server how to parse this data in java .
LabelField jsonResult = new LabelField(connectJson.response);
"[{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"}]"
I am getting response in jsonResult variable
You can use libraries like Jackson to do the same. There is also Google's GSON which will help you do the same. See this example
Take a look at the JSONParser Object in this Tutorial
If you are using Eclipse plugin than may JSON library included in you SDK.
Use below code to parse your JSON string got from the server.
String test = "[{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"},{\"screen_refresh_interval\":4,\"station_list_last_update\":\"update4\"}]";
JSONArray array = new JSONArray(test);
JSONObject obj = (JSONObject) array.get(0);
Your String look like you got JSON Array from the server.
First convert your Json string to JSON Array by
JSONArray array = new JSONArray(Your JSON String);
Each element in array represent JSON Object.You can read JSON Object by
JSONObject obj = (JSONObject) array.get(Index);
You can read parameter from Object to any String variable by :
String valueStr = obj.getString("screen_refresh_interval");
May this help you.
Design a class (viz CustomClass) first with screen_refresh_interval and station_list_last_update as properties. And Make a collection class for CustomClass
I'm using Gson as deserializer. Other libraries are also available.
public class Container {
private CustomClass[] classes;
public CustomClass[] getClasses() {
return classes;
}
public void setClasses(CustomClass[] classes) {
this.classes = classes;
}
}
public class CustomClass {
private String screen_refresh_interval;
private String station_list_last_update;
public String getScreen_refresh_interval() {
return screen_refresh_interval;
}
public void setScreen_refresh_interval(String screen_refresh_interval) {
this.screen_refresh_interval = screen_refresh_interval;
}
public String getStation_list_last_update() {
return station_list_last_update;
}
public void setStation_list_last_update(String station_list_last_update) {
this.station_list_last_update = station_list_last_update;
}
}
Gson gson = new Gson();
Container customClassCollection = gson.fromJson(jsonResult, Container.class);
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...