I have a problem with parsing a JSON response using Gson.
JSON string:
response: [
2, {
owner_id: 23972237,
album_id: 25487692,
title: 'album not new'
}, {
owner_id: 23972237,
album_id: 25486631,
title: 'фыв'
}
]
I have these 2 classes:
public class VkAudioAlbumsResponse {
public ArrayList<VkAudioAlbum> response;
public VkError error;
}
public class VkAudioAlbum {
public int owner_id;
public int album_id;
public String title;
}
But I have an Exception when parse this using Gson.
I know this is because response array first element is not an object, but integer.
So the question is, can I solve it somehow?
You have to write a custom deserializer. I'd do something like this:
First you need to include a new class, further than the 2 you already have:
public class Response {
public VkAudioAlbumsResponse response;
}
And then you need a custom deserializer, something similar to this:
private class VkAudioAlbumsResponseDeserializer
implements JsonDeserializer<VkAudioAlbumsResponse> {
#Override
public VkAudioAlbumsResponse deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonArray jArray = (JsonArray) json;
int firstInteger = jArray.get(0); //ignore the 1st int
VkAudioAlbumsResponse vkAudioAlbumsResponse = new VkAudioAlbumsResponse();
for (int i=1; i<jArray.size(); i++) {
JsonObject jObject = (JsonObject) jArray.get(i);
//assuming you have the suitable constructor...
VkAudioAlbum album = new VkAudioAlbum(jObject.get("owner_id").getAsInt(),
jObject.get("album_id").getAsInt(),
jObject.get("title").getAsString());
vkAudioAlbumsResponse.getResponse().add(album);
}
return vkAudioAlbumsResponse;
}
}
Then you have to deserialize your JSON like:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(VkAudioAlbumsResponse.class, new VkAudioAlbumsResponseDeserializer());
Gson gson = gsonBuilder.create();
Response response = gson.fromJson(jsonString, Response.class);
With this approach, when Gson tries to deserialize the JSON into Response class, it finds that there is an attribute response in that class that matches the name in the JSON response, so it continues parsing.
Then it realises that this attribute is of type VkAudioAlbumsResponse, so it uses the custom deserializer you have created to parse it, which process the remaining portion of the JSON response and returns an object of VkAudioAlbumsResponse.
Note: The code into the deserializer is quite straightforward, so I guess you'll have no problem to understand it... For further info see Gson API Javadoc
Related
I have an enum that looks something like this
public enum Example {
EXAMPLE_1,
EXAMPLE_2,
EXAMPLE_3,
}
I am trying to parse a json string like this:
String json = "{\"blah\": \"Example.EXAMPLE_1\"}"
I have tried defining a class like this:
public class Blah {
Example blah;
}
and using
gson.fromJson(json, Blah.class)
but it just sets the field to null. Is there anyway to do this? I unfortunately can't control the format of the json string so I have to parse it as is.
Default gson will parse enum field without class name. You can customise your own json deserializer for Example enum.
JsonDeserializer<?> jd = new JsonDeserializer<Example>() {
#Override
public Example deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String enumStr = json.getAsString();
String enumVal = enumStr.split("\\."); // etc...
Example val = ... ...
//...
return val;
}
};
Gson gson = new GsonBuilder().registerTypeAdapter(Example.class, jd).create();
Try this json
String json = "{\"blah\": \"EXAMPLE_1\"}"
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'm trying to deserialize the following structure
{ meta: { keywords: [a, b, c, d]} ... }
other valid structures are
{ meta: { keywords: "a,b,c,d"} ... }
and
{ meta: {keywords: "a"} ...}
I have this classes
public class Data {
#PropertyName("meta")
MetaData meta;
...
}
public class MetaData {
List<String> keywords;
...
}
and a custom deserializer
public static class CustomDeserilizer implements JsonDeserializer<MetaData>{
#Override
public MetaData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<String> keywords = null;
Gson gson = new Gson();
MetaData metaData = gson.fromJson(json, AppMetaData.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("keywords")) {
JsonElement elem = jsonObject.get("keywords");
if (elem != null && !elem.isJsonNull()) {
if (jsonObject.get("keywords").isJsonArray()) {
keywords = gson.fromJson(jsonObject.get("keywords"), new TypeToken<List<String>>() {
}.getType());
} else {
String keywordString = gson.fromJson(jsonObject.get("keywords"), String.class);
keywords = new ArrayList<String>();
list.addAll(Arrays.asList(keywordString.split(",")));
}
}
}
metaData.setKeywords(keywords);
}
Then I try to apply the deserilizer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new CustomDeserilizer())
.create();
But I get a parsing error , because is trying to deserialize Data instead of MetaData, how can I apply this deserializer to make it work right?
I solved it creating a deserializer for my class Data.
public static class DataDeserilizer implements JsonDeserializer {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
Data data = gson.fromJson(json, Data.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("meta")) {
JsonElement elem = jsonObject.get("meta");
if (elem != null && !elem.isJsonNull()) {
Gson gsonDeserializer = new GsonBuilder()
.registerTypeAdapter(MetaData.class, new CustomDeserilizer())
.create();
gsonDeserializer.fromJson(jsonObject.get("meta"), Data.class);
}
}
return data;
}
}
And
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new DataDeserilizer())
.create();
Pretty obvious, but is there a more elegant solution?
Firstly, rename your class to meta instead of metadata and make keywords String instead of List.Then use the following to map your JSonString into your object.
Gson gson = new GsonBuilder().create();
Meta meta = gson.from(yourJsonString,Meta.class);
In order to get keywords only, you need this.
JsonObject jsonObject = new JsonObject(yourJSonString);
String data = jsonObject.getJsonObject("meta").getString("keywords");
keywords is a JsonObject not an JsonArray so you can't directly map it
onto List. You can split the string to get keywords in an array.
String keywords[] = data.split(",");
Here's a concise solution that leverages Java inheritance to represent the nested structure; and therefore does not need to provide any actual instance member fields (mappings, etc) for capturing the nested String data that GSON maps.
Step 1: For readability, create an empty object to represent the nested mapping
public class StateRegionCitiesMap extends HashMap<String, List<String>> {
}
Step 2: Add the one line of actual code to do the mapping; no other serialize/deserialize logic to manage
protected void loadContent(JsonObject stateRegionsJsonObject) {
HashMap<String, StateRegionCitiesMap> stateRegionCitiesMap =
mGson.fromJson(
stateRegionsJsonObject,
new TypeToken<HashMap<String, StateRegionCitiesMap>>() {
}.getType()
);
}
Alternatively, you can skip the wrapper class altogether and just directly put <String, List<String>> in the GSON call. However, I find an explicit object helps to inform/remind whoever is reading the code, what the purpose is.
Example JSON:
The class StateRegionCitiesMap represents a multi-tier map structure for say:
[US State] -> [State-Region Key] -> [Sub-Region Key] -> CitiesArray[]
"CA": {
"Central CA": {
"Central Valley": [
"FRESNO",
"VISALIA"
],
"Sacramento Area": [
"SACRAMENTO",
"EL DORADO HILLS"
]
},
This suppose to achieve what you want easily. You should define an inner static class. You can keep nesting classes to define keywords as class Keywords, etc. Just remember to have a field in the containing class, i.e.
in your inner class have private Keywords keywords;
In your Main class:
Gson gson = new Gson();
Data data = gson.fromJson(SOME_JSON_STRING, Data.class);
In a class called Data:
public class Data {
private Meta meta;
static class Meta{
private String[] keywords;
}
}
I'm working on a project that communicates with an API using JSON. This is my first attempt at JSON and I've been away from java for a few/several years, so please bear with me.
Here is an idea of what the data looks like:
String 1:
[{
"apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
String 2:
[{
"apicall2":
[{
"thatField":"thatFieldData",
"someFieldsAreTheSame":"someFieldsAreTheSameData",
"otherFieldsAreNotTheSame":"otherFieldsAreNotTheSame"
}]
}]
As you can see from my data example, the API returns a JSON string that contains the api used. The array inside contains the data. The API's have a lot of data fields in common but they are unrelated beyond that.
EDIT: There are dozens of these API's types that will need to be handled.
What I am trying to do is create a response class that accepts all of the JSON strings and returns an object containing the appropriate data.
For Example:
Gson gson = new Gson(); //Custom TypeAdapter goes here if needed.
Response apicall2 = gson.fromJson(apicall2String, Response.class);
System.out.println(apicall2.thatField); //Prints thatFieldData
System.out.println(apicall2.someFieldsAreTheSame); //Prints someFieldsAreTheSameData
System.out.println(apicall2.otherFieldsAreNotTheSame); //Prints otherFieldsAreNotTheSameData
This is where I am lost. Here is what I have so far. I think I need to use a TypeAdapter here but haven't been able to figure how to apply that to my case.
public class Response { //Change to TypeAdapter possibly?
}
public class apicall1 {
String thisField;
String thatField;
String anotherField;
}
public class apicall2 {
String thatField;
String someFieldsAreTheSame;
String otherFieldsAreNotTheSame;
}
You can use Gson's TypeToken class to deserialize json into object. Below is an example:
JSON:
[{ "apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
Model:
class Response{
private List<Result> apicall1;
class Result{
private String thisField;
private String thatField;
private String anotherField;
public String getThisField() {
return thisField;
}
public void setThisField(String thisField) {
this.thisField = thisField;
}
public String getThatField() {
return thatField;
}
public void setThatField(String thatField) {
this.thatField = thatField;
}
public String getAnotherField() {
return anotherField;
}
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
}
public List<Result> getApicall1() {
return apicall1;
}
public void setApicall1(List<Result> apicall1) {
this.apicall1 = apicall1;
}
}
Converter:
public static void main(String[] args) {
String response = "[{ \"apicall1\": [{ \"thisField\":\"thisFieldData\", \"thatField\":\"thatFieldData\", \"anotherField\":\"anotherFieldData\" }]}]";
Gson gson = new Gson();
List<Response> responses = gson.fromJson(response, new TypeToken<List<Response>>(){}.getType());
System.out.println(responses.get(0).getApicall1().get(0).getThisField());
}
I don't know if you want both adapters in one class. Might not be the best OOP design.
To achieve it you would need to do something like so:
public class DoublyTypeAdapter implements JsonDeserializer<ApiCallTypeParent>
{
Gson gson = new Gson();
#Override
public ApiCallTypeParent deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject json = jsonElement.getAsJsonObject();
ApiCallTypeParent desrializeIntoMe;
// Detect which type to implement
if(apiTypeOne(type) {
desrializeIntoMe = new TypeOne();
} else {
desrializeIntoMe = new TypeTwo();
}
for (Map.Entry<String, JsonElement> entry : json.entrySet())
{
switch(entry.getKey()){
case "thisField":
desrializeIntoMe.setThisField(entry.getValue().getAsString());
break;
......
default: // We don't care
break;
}
}
return desrializeIntoMe ;
}
}
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);