I have a Java app that is getting back the following JSON from a 3rd party RESTful web service:
{
"widgets":[
[
{
"id":25128,
"status":"always",
"uuid":"96f62edd-fa8a-4267-8ffb-14af0d37de26"
}
],
[
{
"id":25200,
"status":"always",
"uuid":"78553c9e-398f-495a-8fb8-ada0fb297844"
}
],
[
{
"id":25128,
"status":"never",
"uuid":"b1e3deb2-a842-4cba-8272-458d15efb394"
}
]
]
}
And trying to convert it into a List<Widget> using GSON:
public class Widget {
#SerializedName("id")
private Long id;
#SerializedName("status")
private String status;
#SerializedName("uuid")
private String uuid;
// Getters & setters, etc.
}
Here is my mapper code:
String jsonResponse = getJsonFromWebService();
Gson gson = new Gson();
List<Widget> widgets = gson.fromJson(jsonResponse, new TypeToken<List<Widget>>(){}.getType());
When I run this, I'm getting the following error:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2
Obviously, I either need to manipulate the JSON string before sending it to my GSON mapper code, or I need to configure GSON to handle the "unexpected" JSON, but I'm not sure which is easier/more appropriate. If I need to "massage" the JSON string, not sure what I need to do to make GSON play nicely with it. And if I need to configure GSON, not sure what to do there either. Any ideas? Thanks in advance.
What's wrong is that you're ignoring the root JSON Object with a single JSON Property "widgets". Try deserializing your data into this object instead:
public class WidgetList {
#SerializedName("widgets")
private List<List<Widget>> widgets;
}
Massaging it to the below format works for me
[
{
'id':25128,
'status':'always',
'uuid':'96f62edd-fa8a-4267-8ffb-14af0d37de26' },
{
'id':25200,
'status':'always',
'uuid':'78553c9e-398f-495a-8fb8-ada0fb297844' },
{ 'id':25128,
'status':'never',
'uuid':'b1e3deb2-a842-4cba-8272-458d15efb394'
}
]
As the below demonstrates
public class TryMe {
public static void main(String[] args) {
Gson gson = new Gson();
List<Widget> widgets = gson.fromJson(json,
new TypeToken<List<Widget>>() {
}.getType());
System.out.println(widgets);
}
}
class Widget {
#SerializedName("id")
private Long id;
#SerializedName("status")
private String status;
#SerializedName("uuid")
private String uuid;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
#Override
public String toString() {
return "Widget [id=" + id + ", status=" + status + ", uuid=" + uuid
+ "]";
}
}
Giving the below resp
[Widget [id=25128, status=always, uuid=96f62edd-fa8a-4267-8ffb-14af0d37de26], Widget [id=25200, status=always, uuid=78553c9e-398f-495a-8fb8-ada0fb297844], Widget [id=25128, status=never, uuid=b1e3deb2-a842-4cba-8272-458d15efb394]]
Related
I have a JSON response that looks something like:
And a Subscription POJO class and inside it, is an Arraylist of the "subscriptionPlans":
SubscriptionDetails.java
#Expose()
#SerializedName("subscriptionPlans")
public ArrayList<SubscriptionPlans> subscriptionPlans;
public ArrayList<SubscriptionPlans> getSubscriptionPlans() {
return subscriptionPlans;
}
#Override
public String toString() {
return "SubscriptionDetails{" +
"subscriptionPlans=" + subscriptionPlans +
'}';
}
SubscriptionPlans.java
#SerializedName("plan_name")
#Expose
public String planName;
#SerializedName("description")
#Expose
public String description;
#SerializedName("amount")
#Expose
public String amount;
public String getPlanName() {
return planName;
}
public String getDescription() {
return description;
}
public String getAmount() {
return amount;
}
I'm using Gson to get the data from the JSON and populate it to the various POJO classes like so:
Gson gson = new Gson();
SubscriptionDetails subscriptionDetails = gson.fromJson(String.valueOf(jsonObject.getJSONArray("subscriptionPlans")), SubscriptionDetails.class);
ArrayList<SubscriptionPlans> subscriptionPlans = subscriptionDetails.getSubscriptionPlans();
String amount = subscriptionPlans.get(0).getAmount();
however, I get the error response,
java.lang.IllegalStateException:Expected BEGIN_OBJECT but was BEGIN_ARRAY at line column 2 path $
What I'm I missing or not doing correct here?
pass to GSON the entire string, not just String.valueOf(jsonObject.getJSONArray("subscriptionPlans")):
SubscriptionDetails subscriptionDetails = gson.fromJson(String.valueOf(jsonObject), SubscriptionDetails.class);
I wanted to use converters to instantly get a POJO from the response, but the root JSON object is not that one what I want to deserialize to a POJO. In fact I want to deserialize it's 2 sibling objects into a POJO.
For example get a stream from the new Twitch API:
#GET("/helix/streams?first=1")
Call<TwitchStream> getLatestStreamsForGame(#Query("game_id") int gameId);
The response will be something like that:
{
"data": [
{
"id": "26007494656",
"user_id": "23161357",
"game_id": "417752",
"community_ids": [
"5181e78f-2280-42a6-873d-758e25a7c313",
"848d95be-90b3-44a5-b143-6e373754c382",
"fd0eab99-832a-4d7e-8cc0-04d73deb2e54"
],
"type": "live",
"title": "Hey Guys, It's Monday - Twitter: #Lirik",
"viewer_count": 32575,
"started_at": "2017-08-14T16:08:32Z",
"language": "en",
"thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_lirik-{width}x{height}.jpg"
},
...
],
"pagination": {
"cursor": "eyJiIjpudWxsLCJhIjp7Ik9mZnNldCI6MjB9fQ=="
}
}
And this is the class in which I want to store the data of a stream:
public class TwitchStream extends DataResponse{
#SerializedName("id")
private String id;
#SerializedName("user_id")
private String userId;
#SerializedName("game_id")
private String gameId;
#SerializedName("community_ids")
private List<String> communityIds;
#SerializedName("type")
private String type;
#SerializedName("title")
private String title;
#SerializedName("viewer_count")
private int viewerCount;
#SerializedName("started_at")
private String startedAt;
#SerializedName("language")
private String language;
#SerializedName("thumbnail_url")
private String thumbnailUrl;
#SerializedName("cursor")
private String paginationCursor;
public TwitchStream(String id, String user_id, String game_id, List<String> community_ids, String type, String title, int viewer_count, String started_at, String language, String thumbnail_url, String paginationCursor) {
this.id = id;
this.userId = user_id;
this.gameId = game_id;
this.communityIds = community_ids;
this.type = type;
this.title = title;
this.viewerCount = viewer_count;
this.startedAt = started_at;
this.language = language;
this.thumbnailUrl = thumbnail_url;
this.paginationCursor = paginationCursor;
}
public String getId() {
return id;
}
public String getUserId() {
return userId;
}
public String getGameId() {
return gameId;
}
public List<String> getCommunityIds() {
return communityIds;
}
public String getType() {
return type;
}
public String getTitle() {
return title;
}
public int getViewerCount() {
return viewerCount;
}
public String getStartedAt() {
return startedAt;
}
public String getLanguage() {
return language;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public String getPaginationCursor() {
return paginationCursor;
}
}
I wanted to deserialize the contents of data into the above POJO, and I also need the cursor from pagination. I wanted to use GsonConverterFactory, but Gson wants to deserialize the root JSON object, and since it can't find any field in this class named or annotated as data and pagination, it had done nothing.
What could I do?
One solution is to make a wrapper class, and create the call this way:
#GET("/helix/streams?first=1")
Call<Wrapper<TwitchStream>> getLatestStreamsForGame(#Query("game_id") int gameId);
But this way I will get an exception:
ResponseBase failed: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 10 path $.data
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 10 path $.data
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:226)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:122)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:217)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:116)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:153)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 10 path $.data
at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:385)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:215)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:122)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:217)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:116)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:153)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
enter code here
An other option is to use JSONParser before Gson tries to deserialize the root JSON object, and only pass the data and the pagination object to Gson for deserialization. But how could I do that?
As your exception states
ResponseBase failed: com.google.gson.JsonSyntaxException:
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was
BEGIN_ARRAY at line 1 column 10 path $.data
it waits json object but encounter with json array. Because in your json structure the "data" is an array:
"data": [ // data is a json array
{
"id": "26007494656",
"user_id": "23161357",
"game_id": "417752",
"community_ids": [
"5181e78f-2280-42a6-873d-758e25a7c313",
"848d95be-90b3-44a5-b143-6e373754c382",
"fd0eab99-832a-4d7e-8cc0-04d73deb2e54"
],
"type": "live",
"title": "Hey Guys, It's Monday - Twitter: #Lirik",
"viewer_count": 32575,
"started_at": "2017-08-14T16:08:32Z",
"language": "en",
"thumbnail_url": "https://static-cdn.jtvnw.net/previews-ttv/live_user_lirik-{width}x{height}.jpg"
},
...
]
So I suggest you to create a root object for your json structure which contains List<Data> (data is your current TwichStream class without pagination variable) and also root object contains another class which represents pagination object.
So you have 3 classes as below:
#Data // comes from lombok
class RootClass {
#SerializedName("data")
private List<Data> datas;
private Pagination pagination;
}
#Data
class Pagination {
private String cursor;
}
#Data
class Data {
// your current implementation without pagination field
}
The JSON you have provided basically needs three classes. They are the following.
public class TwitchStream {
public Data[] data;
public Pagination pagination;
}
public class Pagination {
public String cursor;
}
public class Data {
public String id;
public String thumbnail_url;
public String title;
public String game_id;
public String started_at;
public String[] community_ids;
public String user_id;
public String language;
public String viewer_count;
public String type;
}
Hope you can now parse the JSON response using Gson easily. Let me know if that helps!
I am using GSON library to pass json to server as header.
But it is not generating my expected json.
My Pojo class "TestRequest.java" is like:
public class TestRequest {
private String mobileNumber;
public TestRequest(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
}
Here is my code to call the GSON class to make json:
Gson gson = new Gson();
TestRequest tt = new TestRequest("+8801913000000");
String json = gson.toJson(tt);
My expected json is :
{"mobileNumber":"+8801913000000"}
But I am getting:
{"aIf":"+8801913000000"}
Note: This code was working perfectly 2 days before.
Try to change your pojo class like
public class TestRequest implements Serializable {
#SerializedName("mobileNumber")
private String mobileNumber;
public TestRequest(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
public String getMobileNumber() {
return mobileNumber;
}
public void setMobileNumber(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
}
Let me know if not work
I have a JSON payload that looks like this:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"primary_image": {
"id": 247,
"zoom_url": "www.site.com/in_123__14581.1393831046.1280.1280.jpg",
"thumbnail_url": "www.site.com/in_123__14581.1393831046.220.290.jpg",
"standard_url": "www.site.com/in_123__14581.1393831046.386.513.jpg",
"tiny_url": "www.site.com/in_123__14581.1393831046.44.58.jpg"
}
}
Can I unwrap a specific field and discard all the others? In other words, can I bind this directly to a POJO like this:
public class Product {
private Integer id;
private String name;
private String standardUrl;
}
There are lots of ways. Do you need to deserialize, serialize or both?
One way to deserialize would be to use a creator method that takes the image as a tree node:
public static class Product {
private Integer id;
private String name;
private String standardUrl;
public Product(#JsonProperty("id") Integer id,
#JsonProperty("name") String name,
#JsonProperty("primary_image") JsonNode primaryImage) {
this.id = id;
this.name = name;
this.standardUrl = primaryImage.path("standard_url").asText();
}
}
The creator doesn't have to be a constructor, you could have a static method that is only used for Jackson deserialization.
You'd have to define a custom serializer to reserialize this, though (e.g. a StdDelegatingSerializer and a converter to wrap the string back up as an ObjectNode)
There are different ways to skin this cat, I hope you can use Jackson 2 for this, since it offers great ways to deserialize Json data, one of my favorites deserialization features is the one I'll show you here (using Builder Pattern) because allows you to validate instances when they are being constructed (or make them immutable!). For you this would look like this:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Map;
#JsonDeserialize(builder = Product.Builder.class)
public class Product {
private Integer id;
private String name;
private String standardUrl;
private Product(Builder builder) {
//Here you can make validations for your new instance.
this.id = builder.id;
this.name = builder.name;
//Here you have access to the primaryImage map in case you want to add new properties later.
this.standardUrl = builder.primaryImage.get("standard_url");
}
#Override
public String toString() {
return String.format("id [%d], name [%s], standardUrl [%s].", id, name, standardUrl);
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Builder {
private Integer id;
private String name;
private Map<String, String> primaryImage;
public Builder withId(Integer id) {
this.id = id;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
#JsonProperty("primary_image")
public Builder withPrimaryImage(Map<String, String> primaryImage) {
this.primaryImage = primaryImage;
return this;
}
public Product build() {
return new Product(this);
}
}
}
To test it I created this class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
String serialized = "{" +
" \"id\": 32," +
" \"name\": \"[Sample] Tomorrow is today, Red printed scarf\"," +
" \"primary_image\": {" +
" \"id\": 247," +
" \"zoom_url\": \"www.site.com/in_123__14581.1393831046.1280.1280.jpg\"," +
" \"thumbnail_url\": \"www.site.com/in_123__14581.1393831046.220.290.jpg\"," +
" \"standard_url\": \"www.site.com/in_123__14581.1393831046.386.513.jpg\"," +
" \"tiny_url\": \"www.site.com/in_123__14581.1393831046.44.58.jpg\"" +
" }" +
" }";
ObjectMapper objectMapper = new ObjectMapper();
try {
Product deserialized = objectMapper.readValue(serialized, Product.class);
System.out.print(deserialized.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
The output is (using the override toString() method in Product:
id [32], name [[Sample] Tomorrow is today, Red printed scarf], standardUrl [www.site.com/in_123__14581.1393831046.386.513.jpg].
There are two ways to get the response you required. For both methods, we are going to use JsonView.
Create two types of JsonView:
public interface JViews {
public static class Public { }
public static class Product extends Public { }
}
First method
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonIgnore
private Image primaryImage;
#JsonView(JViews.Product.class)
public String getStandardUrl{
return this.primaryImage.getStandardUrl();
}
}
Second way
Using Jackson's #JsonView and #JsonUnwrapped together.
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonUnwrapped
private Image primaryImage;
}
public class Image {
private String zoomUrl;
#JsonView(JViews.Product.class)
private String standardUrl;
}
#JsonUnwrapped annotation flattens your nested object into Product object. And JsonView is used to filter accessible fields. In this case, only standardUrl field is accessible for Product view, and the result is expected to be:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"standard_url": "url"
}
If you flatten your nested object without using Views, the result will look like:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"id":1,
"standard_url": "url",
"zoom_url":"",
...
}
Jackson provided #JsonUnwrapped annotation.
See below link:
http://jackson.codehaus.org/1.9.9/javadoc/org/codehaus/jackson/annotate/JsonUnwrapped.html
i get a JSON response from a server and i want to transform it into a POJO which is the following:
public class MeasureDataGetPOJO {
#SerializedName("CODE")
private String code = null;
#SerializedName("USER")
private User user = null;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public class User {
private List<MeasureData> measureDatas = null;
public List<MeasureData> getMeasureDatas() {
return measureDatas;
}
public void setMeasureDatas(List<MeasureData> measureDatas) {
this.measureDatas = measureDatas;
}
public class MeasureData {
#SerializedName("MT_TIME")
private String time = null;
#SerializedName("MT_VALUE")
private String value = null;
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
}
The JSON i get from the server:
{
"CODE":"012",
"USER":
[
{
"MT_TIME":"1412882760",
"MT_VALUE":"319",
}
]
}
And the error i get from Gson is:
Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 10
Is there something wrong with the JSON or with the POJO i try to map the JSON into?
I am expecting one user with multiple pairs of MT_TIME and MT_VALUE.
So i get a status code and a user object. the user has a array of pairs of MT_TIME and MT_VALUE.
Later there maybe will be more user informations in the user object.
It is just a guess but would this be the correct json?
{
"CODE":"012",
"USER":
{
"MEASURE_DATA":
[
{
"MT_TIME":"1412882760",
"MT_VALUE":"319"
}
]
}
}
with an additional SerializedName here:
#SerializedName("MEASURE_DATA")
private List<MeasureData> measureDatas = null;
It is expected. From what your class says, the JSON should look like:
{
"CODE": "012",
"USER": {
"MT_TIME": "1412882760",
"MT_VALUE": "319",
}
}
But the user field in the JSON you showed is inside an array.
If this means that what you expect is a list of users instead of just one user, then replace your field in the POJO with a List<User>; otherwise, fix the JSON.
You need list of Users. See that after "USER": there is a [ in your json String.
This is what the error is saying:
Expected BEGIN_OBJECT but was BEGIN_ARRAY