mapping nested json and POJOs using Spring - java

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);

Related

How to map JSON to Java POJO with a dynamic field in JSON?

I would like create a Java object which map this JSON object:
{
"base_currency_code": "HKD",
"base_currency_name": "Hong Kong dollar",
"amount": "150.5800",
"updated_date": "2022-03-20",
"rates": {
"GBP": {
"currency_name": "Pound sterling",
"rate": "0.0975",
"rate_for_amount": "14.6774"
}
},
"status": "success"
}
Only the "GBP" property name is dynamic field, it could be another currency symbol next time like "USD", "JPY" etc.
I create the Java class like this:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"base_currency_code",
"base_currency_name",
"amount",
"updated_date",
"rates",
"status"
})
public class CurrencyConvertDto {
#JsonProperty("base_currency_code")
private String baseCurrencyCode;
#JsonProperty("base_currency_name")
private String baseCurrencyName;
#JsonProperty("amount")
private String amount;
#JsonProperty("updated_date")
private String updatedDate;
#JsonProperty("rates")
private Rates rates;
#JsonProperty("status")
private String status;
/*
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
*/
#JsonProperty("base_currency_code")
public String getBaseCurrencyCode() {
return baseCurrencyCode;
}
#JsonProperty("base_currency_code")
public void setBaseCurrencyCode(String baseCurrencyCode) {
this.baseCurrencyCode = baseCurrencyCode;
}
#JsonProperty("base_currency_name")
public String getBaseCurrencyName() {
return baseCurrencyName;
}
#JsonProperty("base_currency_name")
public void setBaseCurrencyName(String baseCurrencyName) {
this.baseCurrencyName = baseCurrencyName;
}
#JsonProperty("amount")
public String getAmount() {
return amount;
}
#JsonProperty("amount")
public void setAmount(String amount) {
this.amount = amount;
}
#JsonProperty("updated_date")
public String getUpdatedDate() {
return updatedDate;
}
#JsonProperty("updated_date")
public void setUpdatedDate(String updatedDate) {
this.updatedDate = updatedDate;
}
#JsonProperty("rates")
public Rates getRates() {
return rates;
}
#JsonProperty("rates")
public void setRates(Rates rates) {
this.rates = rates;
}
#JsonProperty("status")
public String getStatus() {
return status;
}
#JsonProperty("status")
public void setStatus(String status) {
this.status = status;
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Rates {
private List<Map<String, String>> rateInfo = new ArrayList<Map<String, String>>();
#JsonAnySetter
public void setDynamicProperty(String name, Map<String, String> map) {
rateInfo.add(map);
}
public List<Map<String, String>> getRateInfo() {
return rateInfo;
}
public void setRateInfo(List<Map<String, String>> rateInfo) {
this.rateInfo = rateInfo;
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"currency_name",
"rate",
"rate_for_amount"
})
public class RateInfo {
#JsonProperty("currency_name")
private String currencyName;
#JsonProperty("rate")
private String rate;
#JsonProperty("rate_for_amount")
private String rateForAmount;
/*
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
*/
#JsonProperty("currency_name")
public String getCurrencyName() {
return currencyName;
}
#JsonProperty("currency_name")
public void setCurrencyName(String currencyName) {
this.currencyName = currencyName;
}
#JsonProperty("rate")
public String getRate() {
return rate;
}
#JsonProperty("rate")
public void setRate(String rate) {
this.rate = rate;
}
#JsonProperty("rate_for_amount")
public String getRateForAmount() {
return rateForAmount;
}
#JsonProperty("rate_for_amount")
public void setRateForAmount(String rateForAmount) {
this.rateForAmount = rateForAmount;
}
}
But when compile, it seems have problem. it can't map the JSON object with the dynamic field . Does anyone know how to fix it? Thanks you very much.
You can use #JsonAnyGetter and get the additional dynamic key value pair(s) in a Map. Please refer to the usage of #JsonAnyGetter in the following example and do let me know if you still need help.
https://www.logicbig.com/tutorials/misc/jackson/jackson-any-setter.html
If you are able,
the simplest option would be to use a reasonable JSON design.
Here is an example:
{
"base_currency_code": "HKD",
"base_currency_name": "Hong Kong dollar",
"amount": "150.5800",
"updated_date": "2022-03-20",
"rates": {
"currentySymbol": "GBP",
"currency_name": "Pound sterling",
"rate": "0.0975",
"rate_for_amount": "14.6774"
}
},
"status": "success"
}
May be you were thinking too complicated here.
In your CurrencyConvertDto class, instead of using
#JsonProperty("rates")
private Rates rates;
you can simply use
#JsonProperty("rates")
private Map<String, RateInfo> rates;
(and of course adjust the getRatesand setRates methods accordingly).
And then you don't need the Rates class anymore.
Jackson can cope with this out-of-the-box.
It will handle arbitrary currency codes as keys of the map, like in:
{
"base_currency_code": "HKD",
"base_currency_name": "Hong Kong dollar",
"amount": "150.5800",
"updated_date": "2022-03-20",
"rates": {
"GBP": {
"currency_name": "Pound sterling",
"rate": "0.0975",
"rate_for_amount": "14.6774"
},
"EUR": {
"currency_name": "Euro",
"rate": "0.120",
"rate_for_amount": "18.07"
}
},
"status": "success"
}
And by the way: You don't need to repeat the #JsonProperty
annotations on the getter and setter methods. Putting #JsonProperty
only on the member variables is already enough.

Retrofit: Json parser according to response

I have a REST service and it's response can be change according to status. For example; When I send a request and the response can be two types. The first one like that
{
"status": "success",
"user": {
"user_id": 3554,
"full_name": "test",
"email_address": "test#test1.com",
"end_date": null
}
}
The second type is like that
{
"status": "failure",
"reason": "email_taken"
}
The response according to "status" which comes with response. I searched this problem and find some solutions (custom converter, set custom converter etc.) But I think these are not clear enough . Are there any solution like that; if "status" is success, convert to json response to User model, else convert json response to FailureModel?
Retrofit dependency : implementation 'com.squareup.retrofit:retrofit:1.9.0'
If the only solution is custom converter, please explain it clearly because I am really new on this topic.
It is possible with custom json deserializer. You only have user when the status is success in case it is not you have the reason. In case you have status error and try to access user its null.
public class CustomConvertor implements JsonDeserializer<Response> {
#Override
public Response deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
Response response = gson.fromJson(json, Response.class);
if (response.getStatus().equals("success")) {
// The full response as a json object
final JsonObject jsonObject = json.getAsJsonObject();
// The user attribute in the JSON received
final JsonElement jsonElement = jsonObject.get("user");
User user = gson.fromJson(jsonElement, User.class);
response.setUser(user);
}else{
// you could do this
// not needed as json is deserialized to Response already
// just for the example
final JsonObject jsonObject = json.getAsJsonObject();
String reason = jsonObject.getAsJsonPrimitive("reason").getAsString();
response.setReason(reason);
}
return response;
}
}
The retrofit part
GsonBuilder gsonBuilder =new GsonBuilder();
gsonBuilder.registerTypeAdapter(Response.class, new CustomConvertor());
Gson gson = gsonBuilder.create();
GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);
Retrofit retrofit = new Retrofit.Builder()
...// other setups
.addConverterFactory(gsonConverterFactory).build();
Then
// service is my case
Service service = retrofit.create(Service.class);
// call enqueue in your case.for testing i used mockwebserver
Response response = service.exampleJson().execute().body();
Log.i("User: ","" + response.geUser().getFullname());
in case of error
Log.i("Error: ","" + response.getReason());
You can get your pojos from http://www.jsonschema2pojo.org/
Pojo's
Response.java
public class Response {
#SerializedName("status")
#Expose
private String status;
#SerializedName("user")
#Expose
private User user;
#Expose
#SerializedName("reason")
private String reason;
public void setReason(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
User.java
public class User {
#SerializedName("user_id")
#Expose
private int userId;
#SerializedName("full_name")
#Expose
private String fullName;
#SerializedName("email_address")
#Expose
private String emailAddress;
#SerializedName("end_date")
#Expose
private Object endDate;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public Object getEndDate() {
return endDate;
}
public void setEndDate(Object endDate) {
this.endDate = endDate;
}
}
The other way
Call<Response> auth = .// setup
auth.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, Response<Response> response) {
if (response.isSuccessful() ) {
Response respojo = response.body();
if(respojo.getStatus().equals("success"){
Log.i("User: ","" + respojo.getUser().getFullname());
}else {
Log.i("Error: ","" + respojo.getReason());
}
}
} else {
response.errorBody();
}
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
t.printStackTrace();
}
});
You can use a unique model and handle both cases with it :
public class UserResponseModel{
private String status;
private String reason;
private UserModel user;
// getter/setter
boolean isFailure(){
return status == "failure"
}
boolean isSuccess(){
return status == "success"
}
}
you may then do
UserResponseModel response
if( response.isSuccess() ) // do whatever with response.user
else // do whatever with response.reason
With your retrofit 2.0 best idea is to use Gson converter. Just add #Nullable annotation with your optional json key (in your case user and reason) so it does not crash while parsing or does not generate nullpointer exception. So your model class look like as follows.
public class YourModelClass {
#SerializedName("status")
#Expose
public String status;
#Nullable
#SerializedName("user")
#Expose
public User user;
#Nullable
#SerializedName("reason")
#Expose
public String reason;
public class User {
#SerializedName("user_id")
#Expose
public Integer userId;
#SerializedName("full_name")
#Expose
public String fullName;
#SerializedName("email_address")
#Expose
public String emailAddress;
#SerializedName("end_date")
#Expose
public Object endDate;
}
}
In your Activity or fragment where you are requesting parse it as follows
#Override
public void onResponse(Call<YourModelClass> call, Response<YourModelClass> response) {
if(response.body.yourModelClass.status.equals("succ")) {
User changesList = response.body().user;
//perform action with user data
} else {
Log.d("failer", response.body().reason)
}
}
#Override
public void onFailure(Call<YourModelClass> call, Throwable t) {
t.printStackTrace();
}
I hope its work for you.
Use android studio plugin DTO Genrater for creating pojo.
You can have an englobing class for this, for example:
public class Foo {
String status;
String reason;
UserModel user;
// Constructors, getter/setter, others
// ...
}
then call Retrofit like,
Call<Foo> callToYourAPI();
and when you want to have a user:
if (foo.reason == "success") // or if (foo.user != null)
// do something with foo.user
The conversion is done automatically in such a case. If your problem was having a field that can be of one type or another, you would have needed a converter.

how to get Json objects from json array and use them with model class

I want to link my received json data to my pojo class using gson library.I used volley library to receive the data.What should i do so that whenever i call getter methods from my pojo class then i get the received json data.
My Json data is in this format.
{
"vichList":[ {
id=1,
username="abc....},
{....},
]
}
I want to get this json data into my pojo class.
Vich.java
public class GetfeedResponse {
private List<Vich> vichList;
public List<Vich> getVichList() {
return vichList;
}
public void setVichList(List<Vich> vichList) {
this.vichList = vichList;
}
}
Vich.java
public class Vich {
private int id;
private String username;
private String full_name;
private String createdAt;
private int vich_id;
private String vich_content;
private String city;
private int like_count;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFull_name() {
return full_name;
}
public void setFull_name(String full_name) {
this.full_name = full_name;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public int getVich_id() {
return vich_id;
}
public void setVich_id(int vich_id) {
this.vich_id = vich_id;
}
public String getVich_content() {
return vich_content;
}
public void setVich_content(String vich_content) {
this.vich_content = vich_content;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public int getLike_count() {
return like_count;
}
public void setLike_count(int like_count) {
this.like_count = like_count;
}
}
Here i am getting the json response using volley library.
httpUtil.getrequest(url,this,new VolleyCallback(){
#Override
public void onSuccess(String result){
GetfeedResponse getfeedResponse = new GetfeedResponse();
// for(Vich vich : getfeedResponse.getVichList()){
// }
Log.d("Response Result:",result);
}
How can i get objects from json array and use them with the help of pojo class?
Using Gson
Add the following dependency in your gradle:
implementation 'com.google.code.gson:gson:2.8.5'
In your onSuccess()
GetfeedResponse getfeedResponse=new Gson().fromJson(result, GetfeedResponse.class);
If you wish to use Volley and POJO its better to use custom GSON request. Check this link : Custom GSON request With Volley
GSON:
GetfeedResponse parsed = new Gson().fromJson(response, GetfeedResponse.class);
Jackson:
GetfeedResponse parsed = new ObjectMapper().readValue(response, GetfeedResponse.class);
Additionally, if you wanted to convert only list of Vich items (and you stripped your JSON accordingly) you could do following:
[ {
id=1,
username="abc....},
{....},
]
List<Vich> viches = Arrays.asList(new Gson().fromJson(vichItemsJson, Vich[].class));

Binding json, that has a list, with an object using Jackson

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 !

Obtaining inner class variable from deserialized json using gson

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.

Categories

Resources