I'm trying to access variables from an inner class of a deserialized json object. Below is the code I've used.
package jsonparser;
public class JsonParser {
private long uid = 0;
private String username, secret, filter, machine_id, access_token,
session_key = null;
public JsonParser() {
}
public static class Profile {
private String last_name, first_name, pic_square, name = null;
private long uid = 0;
final JsonParser outer = JsonParser.this;
public String getLast_name() {
return last_name;
}
public void setLast_name(String last_name) {
this.last_name = last_name;
}
public String getFirst_name() {
return first_name;
}
public void setFirst_name(String first_name) {
this.first_name = first_name;
}
public String getPic_square() {
return pic_square;
}
public void setPic_square(String pic_square) {
this.pic_square = pic_square;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
public Profile() {
}
}
}
And in another class:
JsonParser jp = gson.fromJson(Data, JsonParser.class);
where Data looks like:
{
"uid": 123,
"username": "Hello",
"secret": "87920",
"filter": "nf",
"machine_id": "machine_id",
"access_token": "access_token",
"session_key": "123e",
"profile": {
"last_name": "Tan",
"uid": 123,
"first_name": "Sally",
"pic_square": "url.jpg",
"name": "Sally Tan"
}
}
How would I be able to access the last_name in the profile inner class from the jp object?
Add a field
private Profile profile;
public Profile getProfile() { return profile; }
to the outer class JsonParser. Then you can use
jp.getProfile().getLast_name();
Note: The name JsonParser is confusing since it doesn't parse anything - it stores the parse results. Rename it to Config or something like that.
First remove
final JsonParser outer = JsonParser.this;
from your code. This is an illegal construct because this cannot be referenced from a static context.
Then add
private Profile profile;
to your JsonParser class right below the definition of the other members like username, secret,...
With this member in place the GSon Parser will fill it on your call to gson.fromJson(...).
You can add a getter Method to access your sub-structure from outside.
Related
I'm working with an API where all data are wrapped in a custom object (see below), so I cannot use moshi to convert the retrofit body direct to my models. What is the best way to work with moshi in this case?
#COLLECTIONS ENDPOINT
{
"status": 200,
"data": [
{
"id": 28122,
"name": "Abandonei",
"counts": {
"books": 3
}
},
{
"id": 21091,
"name": "Lendo",
"counts": {
"books": 6
}
},
],
"errors": [],
"pagination": {
"after": 2,
"hasNextPage": true
}
}
The same json structure is used in all api endpoints, the default fields are:
{
"status": 200,
"data": [],
"errors": [],
"pagination": {
"after": 1,
"hasNextPage": true
}
}
My Collection model:
public class BookCollection {
public long id;
public String name;
public ArrayList<Book> books;
public BookCollection(long id, String name) {
this.id = id;
this.name = name;
}
}
To avoid create a parent class to each model, I have implemented a way to use a class that receive a generic type.
To put this to work I've changed the Moshi class to Gson.
My model:
public class BookCollection {
public long id;
public String name;
public ArrayList<Book> books;
public BookCollection(long id, String name) {
this.id = id;
this.name = name;
}
}
The wrapper class used to unwrap the json data:
public class ApiWrapper<T> {
public final int status;
public final T data;
public final List<ApiError> errors = new ArrayList<>();
public ApiWrapper(int status, T data, List<ApiError> errors) {
this.status = status;
this.data = data;
this.errors.addAll(errors);
}
}
The Errors class, referenced in the class above:
public class ApiError {
public int code;
public String message;
public String error;
}
Usage:
public interface NetAPI {
#GET("me/collections")
Call<ResponseBody> getCollections(#Header("Authorization") String auth);
}
public class CollectionViewModel extends ViewModel {
private final MutableLiveData<List<Collection>> collections = new MutableLiveData<>();
private final MutableLiveData<Boolean> loading = new MutableLiveData<>();
private final MutableLiveData<Boolean> collectionError = new MutableLiveData<>();
private Call<ResponseBody> call;
private void fetchCollections() {
loading.setValue(true);
call = Api.getInstance().getCollections(TOKEN);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
collectionError.setValue(false);
//THE SECRET
Gson gson = new Gson();
ApiWrapper<List<Collection>> apiResponse = null;
apiResponse = gson.fromJson(response.body().string(), new TypeToken<ApiWrapper<List<Collection>>>(){}.getType());
collections.setValue(apiResponse.data);
loading.setValue(false);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(getClass().getSimpleName(), "Error loading data", t);
collectionError.setValue(true);
loading.setValue(false);
}
});
}
}
At this way I can reuse my ApiWrapper class to any model (Books, Users, Login, etc).
Thanks.
You will need to setup gson/moshi to use your classes that you have created for the json to object mapping. Here is an example of what those java classes would look like. You can use data classes in kotlin as well. For moshi, you will have to create the adapter to help with the json to object mapping.
publci class CollectionResponse {
public int status;
public List<BookCollection> data;
public List<Error> errors;
public Pagination pagination;
}
public class Pagination {
public int after;
public boolean hasNextPage;
}
public class BookCollection {
public long id;
public String name;
public Count counts;
}
public Count {
public int books;
}
public class Error {
}
Below is my JSON data. I want to convert this to POJOs to store the Name,id,profession in a header table and the respective Jsonarray field in a child table.
JSON:
{
"Name": "Bob",
"id": 453345,
"Profession": "Clerk",
"Orders": [
{
"Item": "Milk",
"Qty": 3
},
{
"Item": "Bread",
"Qty": 3
}
]
}
Entity classes:
public class User {
private String name;
private Integer id;
private String Profession;
private JsonArray Orders;
private UserCart userCart;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProfession() {
return Profession;
}
public void setProfession(String profession) {
Profession = profession;
}
public JsonArray getOrders() {
return Orders;
}
public void setOrders(JsonArray orders) {
Orders = orders;
}
public UserCart getUserCart() {
return userCart;
}
public void setUserCart(UserCart userCart) {
this.userCart = userCart;
}
}
public class UserCart {
private String item;
private Integer qty;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public Integer getQty() {
return qty;
}
public void setQty(Integer qty) {
this.qty = qty;
}
}
But when I do below; I get error
Cannot deserialize instance of org.json.JSONArray out of START_ARRAY
token
User user = new User();
JsonNode data = new ObjectMapper().readTree(jsonString);
user = headerMap.readValue(data.toString(), User.class);
How do I go about assigning the entire JSON to both the Java objects ?
Use List<UserCart> for array data in json and use #JsonProperty for mapping different json node name to java object field. No need to use extra field (JsonArray Orders) anymore.
#JsonProperty("Orders")
private List<UserCart> userCart;
I have what I believe is called nested JSON and I want to use Jackson to deserialize into objects. Is it possible to automatically parse the child objects into Java Objects as well if a Program class had for example objects of the type TrackedEntity and ProgramStage (see JSON code) ? Alternatively would it be possible to simply parse the "id" of the respective objects and put them in Strings in the Program objects?
JSON Example is as follows:
{
programs:
[
{
"id": "IpHINAT79UW",
"created": "2013-03-04T10:41:07.494+0000",
"trackedEntity":
{
"id": "cyl5vuJ5ETQ",
"name": "Person"
},
"programStages":
[
{
"id": "A03MvHHogjR",
},
{
"id": "ZzYYXq4EJie",
},
{
"id": "AREMvHHogjR",
},
{
"id": "ZzYYXq4fJie",
}
]
},
{
"id": "IGRINAT79UW",
"created": "2013-03-04T10:41:07.494+0000",
"trackedEntity":
{
"id": "cyl5vuJ5ETQ",
"name": "Person"
},
"programStages":
[
{
"id": "A03MvHHogjR",
},
{
"id": "ZzYYXq4fJie",
},
{
"id": "A01MvHHogjR",
},
{
"id": "ZzGYXq4fJie",
}
]
}
]
}
One approach is simply to create POJOs for the various entities.
If you assume the following for TrackEntity
class TrackedEntity {
private final String id;
private final String name;
#JsonCreator
TrackedEntity(
#JsonProperty("id") final String id,
#JsonProperty("name") final String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
Then the following may be suitable for ProgramStage:
class ProgramStage {
private final String id;
#JsonCreator
ProgramStage(#JsonProperty("id") final String id) {
this.id = id;
}
public String getId() {
return id;
}
}
The Program class is slightly trickier since it must parse som kind of zoned date. I have used the Java 8 ZonedDateTime in this example with a custom formatter. You can also use JSR 310 module as described in this answer.
class Program {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSxx");
private final ZonedDateTime created;
private final String id;
private final List<ProgramStage> programStages;
private final TrackedEntity trackedEntity;
#JsonCreator
public static Program of(
#JsonProperty("id") final String id,
#JsonProperty("created") final String created,
#JsonProperty("trackedEntity") final TrackedEntity trackedEntity,
#JsonProperty("programStages") final List<ProgramStage> programStages) {
return new Program(
id,
ZonedDateTime.parse(created, FORMATTER),
trackedEntity,
programStages);
}
public Program(
final String id,
final ZonedDateTime created,
final TrackedEntity trackedEntity,
final List<ProgramStage> programStages) {
this.id = id;
this.created = created;
this.trackedEntity = trackedEntity;
this.programStages = programStages;
}
public ZonedDateTime getCreated() {
return created;
}
public String getId() {
return id;
}
public List<ProgramStage> getProgramStages() {
return programStages;
}
public TrackedEntity getTrackedEntity() {
return trackedEntity;
}
}
Finally, to fix the outer programs entity the following can be used:
class Programs {
private final List<Program> programs;
#JsonCreator
Programs(#JsonProperty("programs") final List<Program> programs) {
this.programs = programs;
}
public List<Program> getPrograms() {
return programs;
}
}
To use the whole thing, simply instantiate an ObjectMapper and use the readValue method like this:
final Programs programs = new ObjectMapper().readValue(json, Programs.class);
Yes. You should be fine. Crate a data structure which represents your data:
public class Container
{
public List<ProgramInfo> programs {get;set;}
}
public class ProgramInfo
{
public string id{get; set;}
public DateTime created{get;set;}
public TrackEntity trrack{get;set;}
}
public class TrackEntity
{
public string id{get;set;}
public string name{get;set;}
}
//Then call the deserialise or serialize
Container container = new JavaScriptSerializer().Deserialize<Container>(yourString);
public class TrackedEntity
{
public string id { get; set; }
public string name { get; set; }
}
public class ProgramStage
{
public string id { get; set; }
}
public class Program
{
public string id { get; set; }
public string created { get; set; }
public TrackedEntity trackedEntity { get; set; }
public List<ProgramStage> programStages { get; set; }
}
public class RootObject
{
public List<Program> programs { get; set; }
}
//Then call the deserialise or serialize
var container = new JavaScriptSerializer().Deserialize<RootObject>(inputjson);
hope it works for you.
I am implementing a REST API which send and receive data with json(I am totally new to this API design). I am using Spring framework and requestbody/responsebody for mapping.
Initially, I had a pojo like this:
public class Action implements Serializable {
#Id
private String id;
private String name;
private String applicationId;
private String timeStamp;
private String username;
private String options;
//Getters and Setters
}
and the json format for this pojo is like this:
{
"id": "11954cd5-eec3-4f68-b0e8-a4d9b6a976a9",
"name": "kill button",
"applicationId": "34fa7bbf-e49f-4f2a-933a-de26b9fdb0f1",
"timeStamp": "2014-03-05T11:51+0000",
"username": "user1783",
"options": "facebook app"
}
This is how the controller look like:I do not get any json, Spring is converting already to java object, should it do it manually myself?
#RequestMapping(value = "applications/{appId}/actions", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public Action addAction(#PathVariable String appId, #RequestBody Action action) {
return actionService.add(appId, action);
}
you can find a pretty json format of it here:
https://gist.github.com/bakharzy/8948950
I want to change the last pair in the json to be a json itself as it is shown in the second json format in gist. So user can send more information. Now that I have a new format for json which is kind of json in json, how should I change the pojo (private String options;) to store the data coming from second json format. Note that the inner json can have arbitrary number of pairs.
My first idea is to change the options in pojo to something like Hash object. Is it doable? If so, how?
Thanks
Just use a nested Object like so:
public class Action implements Serializable {
#Id
private String id;
private String name;
private String applicationId;
private String timeStamp;
private String username;
private Map<String, String> options;
//Getters and Setters
}
This will give you this format:
{
"id": "11954cd5-eec3-4f68-b0e8-a4d9b6a976a9",
"name": "kill button",
"applicationId": "34fa7bbf-e49f-4f2a-933a-de26b9fdb0f1",
"timeStamp": "2014-03-05T11:51+0000",
"username": "user1783",
"options":{
"data": "Click Here",
"size": "36",
"application":"facebook app"
}
}
UPDATE: - Adding test to prove that the solution does indeed work.
public class ActionTest {
#Test
public void testObjectToJson() throws JsonProcessingException {
Action action = new Action();
action.setId("id");
action.setUsername("username");
action.setApplicationId("applicationId");
action.setName("name");
action.setTimeStamp("timestamp");
Map<String, String> map = Maps.newHashMap();
map.put("key", "value");
map.put("key2", "value2");
action.setOptions(map);
ObjectMapper mapper = new ObjectMapper();
String value = mapper.writeValueAsString(action);
System.out.println(value);
}
#Test
public void testJsonToObject() throws IOException {
String json = "{\"id\":\"id\",\"name\":\"name\",\"applicationId\":\"applicationId\",\"timeStamp\":\"timestamp\",\"username\":\"username\",\"options\":{\"key\":\"value\", \"key2\":\"value2\"}}";
ObjectMapper mapper = new ObjectMapper();
Action value = mapper.readValue(json, Action.class);
System.out.println(value);
}
}
class Action {
private String id;
private String name;
private String applicationId;
private String timeStamp;
private String username;
private Map<String, String> options;
public Action() {}
#Override
public String toString() {
final StringBuffer sb = new StringBuffer("Action{");
sb.append("id='").append(id).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", applicationId='").append(applicationId).append('\'');
sb.append(", timeStamp='").append(timeStamp).append('\'');
sb.append(", username='").append(username).append('\'');
sb.append(", options=").append(options);
sb.append('}');
return sb.toString();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Map<String, String> getOptions() {
return options;
}
public void setOptions(Map<String, String> options) {
this.options = options;
}
}
Map<String, Object> innerMap = new WhateverMap<String, Object>();
innerMap.put("data", "click here");
innerMap.put("size", "36");
innerMap.put("application", "facebook app");
Map<String, Object> outerMap = new WhateverMap<String, Object>();
outerMap.put("name", "kill button");
outerMap.put("username", "user1783");
outerMap.put("options", innerMap);
String jsonString = jsonEncoder.encode(outerMap);
Given I have the following json:
{
"Company": {
"name": "cookieltd",
"type": "food",
"franchise_location": [
{
"location_type": "town",
"address_1": "5street"
},
{
"location_type": "village",
"address_1": "2road"
}
]
}
}
How can it be binded to the following object classes using Jackson?:
1) Company class
public class Company
{
String name, type;
List<Location> franchise_location = new ArrayList<Location>();
[getters and setters]
}
2) Location class
public class Location
{
String location_type, address_1;
[getters and setters]
}
I have done:
String content = [json above];
ObjectReader reader = mapper.reader(Company.class).withRootName("Company"); //read after the root name
Company company = reader.readValue(content);
but I am getting:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "franchise_location"
As far as I can tell, you are simply missing an appropriately named getter for the field franchise_location. It should be
public List<Location> getFranchise_location() {
return franchise_location;
}
(and the setter)
public void setFranchise_location(List<Location> franchise_location) {
this.franchise_location = franchise_location;
}
Alternatively, you can annotate your current getter or field with
#JsonProperty("franchise_location")
private List<Location> franchiseLocation = ...;
which helps to map JSON element names that don't really work with Java field name conventions.
The following works for me
public static void main(String[] args) throws Exception {
String json = "{ \"Company\": { \"name\": \"cookieltd\", \"type\": \"food\", \"franchise_location\": [ { \"location_type\": \"town\", \"address_1\": \"5street\" }, { \"location_type\": \"village\", \"address_1\": \"2road\" } ] } }";
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.reader(Company.class).withRootName(
"Company"); // read after the root name
Company company = reader.readValue(json);
System.out.println(company.getFranchise_location().get(0).getAddress_1());
}
public static class Company {
private String name;
private String type;
private List<Location> franchise_location = new ArrayList<Location>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Location> getFranchise_location() {
return franchise_location;
}
public void setFranchise_location(List<Location> franchise_location) {
this.franchise_location = franchise_location;
}
}
public static class Location {
private String location_type;
private String address_1;
public String getLocation_type() {
return location_type;
}
public void setLocation_type(String location_type) {
this.location_type = location_type;
}
public String getAddress_1() {
return address_1;
}
public void setAddress_1(String address_1) {
this.address_1 = address_1;
}
}
and prints
5street
my solution for JSON is always GSON, you can do some research on that, as long as you have the correct structure of class according to the JSON, it can automatically transfer from JSON to object:
Company company = gson.fromJson(json, Company.class);
GSON is so smart to do the convertion thing!
enjoy GSON !