How to parse complex JSON with Retrofit 2 in Android Studio - java

I am trying to get the stopId from this API but I am having a hard time parsing it using retrofit 2 + gson. I've only got experience with less complicated JSON API's. Could anyone help me?
{
"direction": "inbound",
"timetable": {
"$type": "Tfl.Api.Presentation.Entities.Timetable, Tfl.Api.Presentation.Entities",
"departureStopId": "940GZZCRECR",
"routes": [{
"$type": "Tfl.Api.Presentation.Entities.TimetableRoute, Tfl.Api.Presentation.Entities",
"stationIntervals": [{
"$type": "Tfl.Api.Presentation.Entities.StationInterval, Tfl.Api.Presentation.Entities",
"id": "0",
"intervals": [{
"$type": "Tfl.Api.Presentation.Entities.Interval, Tfl.Api.Presentation.Entities",
"stopId": "940GZZCRLEB",
"timeToArrival": 2
}, {
"$type": "Tfl.Api.Presentation.Entities.Interval, Tfl.Api.Presentation.Entities",
"stopId": "940GZZCRSAN",
"timeToArrival": 3
}]
}, {
}, {
}],
"schedules": [
]
}]
}
}

Create your models automatically with this tool. Just paste an example json response.
http://pojo.sodhanalibrary.com
Remember to check and edit types of your variables, sometimes they can be null. After that make your call as usual.

You have to create proper models hierarchy, for example:
BaseModel:
public class BaseModel {
String direction;
Timetable timetable;
}
Timetable:
public class Timetable {
String $type;
String departureStopId;
List<Route> routes;
}
Route:
public class Route {
String $type;
List<StationInterval> stationIntervals;
List<Integer> schedules;
}
StationInterval:
public class StationInterval {
String $type;
int id;
List<Interval> intervals;
}
Interval:
public class Interval {
String $type;
String stopId;
int timeToArrival;
}
And make retrofit call as usual:
#GET("some_url")
Call<BaseModel> loadSomeData();

A simple and efficient way of generating POJO from JSON is http://www.jsonschema2pojo.org/
After you have included the models generated from the above link, you can continue reading this if you need some info setting up Retrofit 2.0.
Now, you would have to define a interface for the APIs
public interface MyAPI {
#GET("/url")
Call<ResponseModel> getData();
}
Then create a class to get the retrofit client
public class MyDataClient {
public static final String BASE_URL = "";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(logging);
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
}
return retrofit;
}
}
Then when you need to call the API do this,
MyAPI apiService =MyDataClient.getClient().create(MyAPI.class);
Call<ResponseModel> call = apiService.getData();
call.enqueue(new Callback<ResponseModel>() {
#Override
public void onResponse(Call<ResponseModel> call, Response<ResponseModel> response) {
}
#Override
public void onFailure(Call<ResponseModel> call, Throwable t){
}
});

Related

Spring WebClient Call Two Dependent API

I have two endpoints, the first is to http://localhost:8899/api/composition/appraisal, which will returns all performance appraisal data
[
{
"appraisalId": "ac234fbf-740c-4390-89d4-0e9753ad4d70",
"employeeId": "emp-4613",
"grade": "VERY_GOOD",
"score": 94,
"status": "NEW"
},
{
"appraisalId": "e9baf663-aa6e-4af8-ae16-ef50c886df28",
"employeeId": "emp-4623",
"grade": "VERY_GOOD",
"score": 94,
"status": "NEW"
}
]
I also have another endpoint to get bonus data http://localhost:8888/api/composition/bonus/{appraisal_id} which return bonus data based on appraisal id
{
"appraisalId": "ac234fbf-740c-4390-89d4-0e9753ad4d70",
"bonusAmount": 4760.0,
"bonusPaidDate": "2019-06-30",
"employeeId": "emp-4623",
"paidToBankAccount": "8240996587"
}
Giving the appraisal_id from first API as parameter, I must merge the result from both API calls into PerformanceAppraisalWithBonus.java class
public class PerformanceAppraisalWithBonus {
private UUID appraisalId;
private double bonusAmount;
private LocalDate bonusPaidDate;
private String employeeId;
private String grade;
private int score;
private String status;
getters / setters
}
How can I achieve this using Spring WebClient?
assuming that the second api http://localhost:8888/api/composition/bonus/{appraisal_id} replies 204 No content when it does not find or does not exist the id and in turn assuming that the expected response is a list of PerformanceAppraisalWithBonus (Flux)
then it would look like this:
webClient
#Component
public class TestClient {
public Flux<Appraisal> firstServiceList() {
return WebClient.create()
.get()
.uri("http://demo4307830.mockable.io/first")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(Appraisal.class);
}
public Mono<AppraisalBonus> firstServiceFindId(UUID uuid) {
return WebClient.create()
.get()
.uri("http://demo4307830.mockable.io/"+ uuid)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(AppraisalBonus.class);
}
}
Service
#Service
public class AppraisalServiceImpl {
#Autowired
private TestClient testClient;
public Flux<AppraisalCombine> combineTwo() {
return testClient.firstServiceList()
.flatMap(appraisal -> testClient.firstServiceFindId(appraisal.getAppraisalId())
.flatMap(appraisalBonus -> Mono.just(AppraisalCombine.builder()
.appraisalId(appraisalBonus.getAppraisalId())
.bonusAmount(appraisalBonus.getBonusAmount())
.bonusPaidDate(appraisalBonus.getBonusPaidDate())
.employeeId(appraisalBonus.getEmployeeId())
.grade(appraisal.getGrade())
.score(appraisal.getScore())
.status(appraisal.getStatus())
.build())));
}
}
Controller
#RestController
#RequestMapping("/appraisal")
public class AppraisalController {
#Autowired
AppraisalServiceImpl appraisalService;
#GetMapping(value = "/get")
public Flux<AppraisalCombine> uploadSimple() {
return appraisalService.combineTwo();
}
}
result
[
{
"appraisalId": "ac234fbf-740c-4390-89d4-0e9753ad4d70",
"bonusAmount": 4760.0,
"bonusPaidDate": "2019-06-30",
"employeeId": "emp-4623",
"grade": "VERY_GOOD",
"score": 94,
"status": "NEW"
}
]

How to Parse only a portion of a web JSON in Java using RestTemplate?

I'm trying to get the first 5 articles from this API: https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21
My code works perfectly for now, but it parses all 10 articles of NewsAPI.
The code is:
public News parse() {
return restTemplate.getForObject
("https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21", News.class);
}
}
and the result is:
{
"totalResults": 10,
"articles": [
{
"source": {
"id": "bbc-news",
"name": "BBC News"
},
"author": "BBC News",
"title": "Measles returns to four European nations, WHO says",
"url": "http://www.bbc.co.uk/news/health-49507253"
},
etc......
Of course, i created the classes that describe Article, Source and News. News has a List of Article.
I just want to parse the first five articles and save them into a List. I know I have to use a For cycle, but how can i do that? I tried with this code:
public News parseFive() {
List<Article> articleList = null;
for(int i = 0; i<5; i++) {
articleList = Arrays.asList(
new Article(restTemplate.getForObject
("https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21", Article.class)));
}
News news = new News();
news.setArticles(articleList);
return news;
}
The News class is:
public class News {
private int totalResults;
private List<Article> articles;
public News() {
}
public int getTotalResults() {
return totalResults;
}
public void setTotalResults(int totalResults) {
this.totalResults = totalResults;
}
public List<Article> getArticles() {
return articles;
}
public void setArticles() {
this.articles = articles;
}
}
and the result is:
{
"totalResults": 0,
"articles": [
{
"source": null,
"author": null,
"title": null,
"url": null
}
]
}
Where is the problem? Maybe because the first class who finds is not Article but is News? Thanks everyone for the effort.
When you are using RestTemplate.getForObject you are technically parsing the whole response: Spring reads all the bytes and uses JSON parser (Jackson) to create an object. Your for loop, which is covered later, only filters out elements past 5th. If you really want to parse only first 5 articles, you should consider using Jackson Streaming API. It is quiet problematically to use with RestTemplate, read this answer for more info.
Now let's try to fix your parseFive.
First, create a class to capture whole response:
public class Response {
private String status;
private Integer totalResults;
private List<Artice> articles;
// Getters & Setters
}
Now, get first five articles:
public News parseFive() {
final Response response = restTemplate
.getForObject("https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21", Response.class);
final News news = new News();
news.setArticles(response.articles.stream().limit(5).collect(Collectors.toList());
return news;
}
You have not provided your News class, probably it is the same as response. Then, the code may look like:
public News parseFive() {
final News news = restTemplate
.getForObject("https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21", Response.class);
news.setArticles(news.articles.stream().limit(5).collect(Collectors.toList());
return news;
}

Implement background tasks to fetch tours data (fetch, put to database, put to search indices)

I am working with vertx and java, I am new to vertx and i have an api. please go to link below for a better understanding
https://www.tez-tour.com/tariffsearch/hotels?countryId=1104&cityId=345&locale=en (countryId (destination)= Turkey, cityId (city of depature)= Moscow)
and a python example script to fetch data from tour api
import asyncio
from aiohttp import ClientSession
import json
async def do_request(url):
async with ClientSession() as session:
async with session.get(url) as response:
resp = await response.read()
#print(str(resp, 'utf-8'))
parsed = json.loads(str(resp, 'utf-8'))
print(parsed)
tasks = []
tourCities = {
/*'Turkey': {
'cities': [1285,12689,12706,143330,9004247,4433,5736,139343,4434,12691,21301,12705,149827,4151426]
},*/
'Austria': {
'tourId': [308122,3024267,147579,353869,320460,3024283,253138,3026464,3024262,293808,469713,3024272,314293,467029,348518,544505,384331,594027,3025654,258494],
'params': {
'hotelClasses': [ // Типы отелей
{"classId": 269506,"name": "Special Cat.","weight": -9},
{"classId": 261942,"name": "Chalet","weight": -8},
{"classId": 253005,"name": "Apts","weight": -6},
{"classId": 253006,"name": "Pens","weight": -5},
],
"tourTypes": [ // Состав тура
{"id": 1,"name": "Полный пакет","haveResidence": true,"haveTransfer": true,"haveFly": true,"haveInsurance": true},
{"id": 2,"name": "Проживание+трансфер","haveResidence": true,"haveTransfer": true,"haveFly": false,"haveInsurance": true},
],
"pansion": [ // Пансион
{"rAndBId": 15350,"name": "Размещение без питания","weight": 0,"sortOrder": 0},
{"rAndBId": 2424,"name": "Только завтраки","weight": 1,"sortOrder": 1},
{"rAndBId": 2474,"name": "Завтрак и ужин","weight": 3,"sortOrder": 3},
],
"tours": [ // Регионы (города, куда ищем тур)
{"name": "Бад-Кляйнкирхайм","tourId": [308122]},
{"name": "Баден","tourId": [3024267]},
tour operator has a web site (or external REST API) where from we can fetch tour data
each of them provide us with authentication data (login & password) to connect to their tour database (no jdbc, only web based access)
So i have some interface to be implemented and i should use WebClient but i dont fully understand how to write this method to fetch from the api above
I have two method to implement as follows
#Override
public YuService runParserTask(String tourOperator, Handler<AsyncResult<Void>> handler) {
return this;
}
#Override
public YuService getTaskStatus(String tourOperator, Handler<AsyncResult<ParseTask>> handler) {
return this;
}
and a parser dto with ENUM status as follows
#DataObject(generateConverter = true)
public class ParseTask {
private String type;
private Status status;
public ParseTask(String type, Status status) {
this.type = type;
this.status = status;
}
public ParseTask(JsonObject json) {
ParseTaskConverter.fromJson(json, this);
}
public JsonObject toJson() {
JsonObject json = new JsonObject();
ParseTaskConverter.toJson(this, json);
return json;
}
Can i get an explanation may be a bit of code to help me get a better understanding on how to implement this method

Retrofit Put Request for Mlab

Hi I'm trying to send a PUT request using Retrofit that uses $addToSet to my Mlab Server. I can do this using Postman but I'm having trouble doing it using Retrofit.
The collection looks like:
[
{
"_id": {
"$oid": "5abe74bac2ef1603f4045686"
},
"email": "test#gmail.com",
"completedWalks": [
"South Leinster Way"
],
"favWalks": []
}
]
The post man request has the API key, Query, and then $addToSet is passed in the body as so.
And the response is:
I'm trying to do it like this in android.
Retrofit:
#PUT("databases/walks/collections/user")
Call<Update> addCompleted (#Query("apiKey") String apiKey,#Query("q") String Email, #Body Update Query);
My model:
public class Update {
#SerializedName("n")
private String n;
public String getN() {
return n;
}
public Update(String n) {
this.n = n;
}
}
Creating the update object:
String updateComplete = String.format("'$addToSet': {'completedWalks': '%s'}} ", TrailName);
final String query =String.format("{'email': '%s'}",email) ;
final Update queryComplete = new Update(updateComplete);
And the Request:
Call<Update> completeCall = apiService.addCompleted(mlabAPi, query, queryComplete);
completeCall.enqueue(new Callback<Update>() {
#Override
public void onResponse(Call<Update> call, Response<Update> response) {
Toast.makeText(getApplicationContext(),"Walk marked as Complete", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<Update> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});
But this only overwrites whats in the collection and I have:
[
{
"_id": {
"$oid": "5abe74bac2ef1603f4045686"
},
"n": "'$addToSet': {'completedWalks': 'Kerry Head Cycleway'}} "
}
]
Does anyone know where I'm going wrong, should I not be passing $addToSet as a model because it seems to be overwriting all, how do I pass it then?
Thank You.
#Body Update Query -- Retrofit will encode the object passed to this as JSON (assuming you are using the Gson converter, which it appears you are). That is where this "n": "'$addToSet': {'completedWalks': 'Kerry Head Cycleway'}} " is coming from. You need to structure you Java Object the same as your JSON object for gson to serialize it correctly.
I am not familiar with the mlab api, but from your postman, it looks like you want a request body something like this --
public class UpdateRequest {
#SerializedName("$addToSet")
Map<String, String> addToSet = new HashMap();
}
Update your interface to send this object as the body --
#PUT("databases/walks/collections/user")
Call<Update> addCompleted (#Query("apiKey") String apiKey,#Query("q") String Email, #Body UpdateRequest Query);
And create the request body --
UpdateRequest requestBody = new UpdateRequest();
requestBody.addToSet.put("completedWalks", Trailname);
and create the call --
Call<Update> completeCall = apiService.addCompleted(mlabAPi, query, requestBody);
For further debugging, you can see what is actually being sent in your logcat by adding HttpLoggingInterceptor to your retrofit instance.
See here for setup. Then you can compare what your app is sending vs postman and see where things might be going sideways.

JSON Post Response from JSON Post Request

I have created a REST webservice which gives me
'GET JSON Response' as :
{
"payload": {
"RFID": "E2005180040F003122202E5F",
"chassisNumber": "4654689761",
"vehicleNumber": "TN 01 1991"
},
"success": "true"
}
Now I want Post Response from below Post Request :
Vehicle tag Request
{
"vehicle_no": "TN07B0054"
}
I have created the post method but it takes the whole thing as argument.
How to take vehicle argument as "TN07B0054" only from the Vehicle tag request.
Below is the POST response when I give above Vehicle Tag Request :
{
"payload": {
"vehicleNumber": "\"vehicle_no\": \"TN 07 B 0054\""
},
"success": "false"
}
You can make a entity named VehicleTagRequest
public class VehicleTagRequest {
private String vehicle_no;
public String getVehicle_no() {
return vehicle_no;
}
public void setVehicle_no(String vehicle_no) {
this.vehicle_no = vehicle_no;
}
}
Easiest way to deserialise your string to above java object is to use Jackson library (https://github.com/FasterXML/jackson)
If you are Maven for dependency management, you can add dependency like below in your pom.xml (this step is not required if you are maintaining your dependencies locally)
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0.pr4</version>
</dependency>
Now comes deserialisation of your vehicle tag request json
Deserialisation using Jackson is done using ObjectMapper API if you are not using annotations,
I have made a sample code snippet attached below-
public class VehicleSerialisation {
public static void main(String[] args) {
String vehicle = "{\"vehicle_no\": \"TN07B0054\"}";
ObjectMapper objectMapper = new ObjectMapper();
VehicleTagRequest vehicleTagRequest = null;
try {
vehicleTagRequest = objectMapper.readValue(vehicle, VehicleTagRequest.class);
} catch (IOException e) {
e.printStackTrace();
}
System.out.print(vehicleTagRequest.getVehicle_no());
}
}
You can then use the vehicleTagRequest.getVehicle_no() to form your request of GET JSON Response
This is not valid Json
{
"payload": {
"vehicleNumber": "\"vehicle_no\": \"TN 07 B 0054\""
},
"success": "false"
}
Without escape char
{
"payload": {
"vehicleNumber": "vehicle_no": "TN 07 B 0054"
},
"success": "false"
}
You can use like this
{
"payload": {
"vehicleNumber": {
"vehicle_no": "TN 07 B 0054"
}
},
"success": "false"
}
And your POJO should be like
public class MyPojo
{
private Payload payload;
private String success;
public Payload getPayload ()
{
return payload;
}
public void setPayload (Payload payload)
{
this.payload = payload;
}
public String getSuccess ()
{
return success;
}
public void setSuccess (String success)
{
this.success = success;
}
#Override
public String toString()
{
return "ClassPojo [payload = "+payload+", success = "+success+"]";
}
}

Categories