TL;DR:
My questions are:
1 - How can I make an Adapter for the "timestamp": 1515375392.225 to ZonedDateTime.
2 - How can I register the List<Report> adapter in the moshi object Builder if I need the moshi object to get this adapter, according to the documentation?
My JSON string has the following structure:
[
{
"id": 0,
"location": {
"latitude": -22.967049,
"longitude": -43.19096
},
"timestamp": 1515375392.225
},
{
"id": 0,
"location": {
"latitude": -22.965845,
"longitude": -43.191102
},
"timestamp": 1515375392.225
},
.......
}]
The timestamp is an automatic conversion made by the Jackson JavaTimeModule, it converts a ZonedDateTime to a timestamp String in the form of a decimal number representing the seconds and nanoseconds from an Instant.
In order to parse the JSON timestamp String, I made the following Moshi adapter:
public class ZonedDateTimeAdapter {
#FromJson ZonedDateTime fromJson(String timestamp) {
int decimalIndex = timestamp.indexOf('.');
long seconds = Long.parseLong(timestamp.substring(0, decimalIndex));
long nanoseconds = Long.parseLong(timestamp.substring(decimalIndex));
return Instant.ofEpochSecond(seconds, nanoseconds).atZone(ZoneId.systemDefault());
}
#ToJson String toJson(ZonedDateTime zonedDateTime) {
Instant instant = zonedDateTime.toInstant();
return instant.getEpochSecond() + "." + instant.getNano();
}
}
And then I register this adapter as:
Type type = Types.newParameterizedType(List.class, Report.class);
Moshi moshi = new Moshi.Builder().add(new ZonedDateTimeAdapter()).build();
JsonAdapter<List<Report>> reportAdapter = moshi.adapter(type);
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build();
The problem is, when I call my webservice using Retrofit, I get the following Exception:
com.squareup.moshi.JsonDataException: java.lang.NumberFormatException:
For input string: ".067000000" at $[0].timestamp
(keep in mind the nanoseconds .067000000 here won't be the same as the JSON example that I gave before, since they called the webservice at different times).
I tried to place a breakpoint on my ZonedDateTimeAdapter, but it's never being called. But it's influencing Moshi, because if I remove it from the Moshi.Builder, the error changes to:
Caused by: java.lang.IllegalArgumentException: Cannot serialize
abstract class org.threeten.bp.ZoneId
I also tried to change the ZonedDateTimeAdapter to deal with Double instead of String, but it just changes the error message to:
com.squareup.moshi.JsonDataException: java.lang.NumberFormatException:
For input string: ".515376840747E9" at $[0].timestamp
So, basically, I have a bunch of changing error messages and no idea what am I doing wrong. I followed the Moshi documentation on Custom Adapters and I don't know what else to do.
Your JSON adapter’s #ToJson methods is accepting a String but the timestamp is a number. Either change this to be a number (ie. double) or pass a JsonReader instead of a String and read the number out yourself. In this case you can call reader.nextString().
Related
I'm not sure if polymorphic is the right term to use so my apologies.
I'm working with the following API:
Request body:
{
"user_id": "user_id",
"command": "submit_document",
}
Response:
{
"result": "success",
"code": 200,
"status": "ok",
"screen": "documents_rejected", // This is unique for different `data`
"next_screen": "",
"message": "Successful",
"data": {
// `data` is always a json object with known fields and parameters
}
}
I have data classes ready for different types of data responses like:
data class PhoneData(
#SerializedName("phone_number")
val phoneNumber: String? = null,
#SerializedName("phone_status")
val phoneStatus: String? = null
)
for "screen": "phone" and the following for another screen:
data class Data(
val deepLink: String? = null
)
The problem is, at the start, I have to make the following request to retrieve the current screen:
{
"user_id": "user_id",
"command": "get_current_screen",
}
which returns a similar response as above:
{
"result": "success",
"code": 200,
"status": "ok",
"screen": "main_screen", // Different types of screen types are known.
"next_screen": "",
"message": "Successful",
"data": {
// `data` is always a json object but the object could contain anything depending on the `screen` type.
}
}
but the data field could contain anything depending on the screen
data class SplashScreenData(
// How do I make this data class combine all other data classes? One ugly approach is to add all the fields from different `data` classes here and use this one only.
)
I found about the RuntimeTypeAdapterFactory for polymorphic cases but am not sure how to make it work when there's no "type" like field within the data object (screen is unique but it's outside the data object).
It would be very helpful if someone has a solution or could point me in a direction.
val frameTextReceived: String = frame.readText()
val jsonObject = JsonParser.parseString(frameTextReceived).asJsonObject
val type = when (jsonObject.get("type").asString) {
TYPE_JOIN_ROOM -> JoinRoom::class.java
TYPE_GAME_MOVE -> GameMove::class.java
TYPE_DISCONNECT_REQUEST -> DisconnectRequest::class.java
else -> BaseModel::class.java
}
val payload = gson.fromJson(frameTextReceived, type)
This is my solution, here I have type parameter by which I can know in which class I have to deserialize the object but in your case you have screen parameter, you can use this.
Suppose if I've a string as
"[{"key1": value1}, {"key2": value2}, and so on...]"
is there a way to convert it into array:
[{"key1": value1}, {"key2": value2}, and so on...]
Actually I've to post a JSON request in my android app which should be in the following format:
{
"user_id": "5665",
"restaurant_id": "5",
"total_cost": "660",
"food":[
{"food_item_id": "50"},
{"food_item_id": "51"}
]
}
As in the above code, I've to use the array for food key which is stuck in string which I get using the following code:
val gson = Gson()
val menuIds = gson.toJson(listOfMenuIds)
You can create a dataclass for food_items to represent a item in the "food" array.
Then create another dataclass which has "user", "restaurant_id", ... so on... as members, including a member var foods: List
Then pass the second class to the Gson converter
I need to build list of places (from response array create instance of place and finally receive list of places (place in json))?
How to parse it using Gson and Retrofit with custom deserializer?
I have following strucutre:
{
"success": true,
"error": null,
"response": [
{
"loc": {
"lat": 51.50853,
"long": -0.12574
},
"place": {
"name": "London",
"state": "",
"stateFull": "",
"country": "GB",
"countryFull": "United Kingdom",
"region": "",
"regionFull": "",
"continent": "eu",
"continentFull": "Europe"
},
"profile": {
"elevM": 21,
"elevFT": 69,
"pop": 7556900,
"tz": "Europe/London",
"tzname": "BST",
"tzoffset": 3600,
"isDST": true,
"wxzone": null,
"firezone": null,
"fips": null,
"countyid": null
}
},
.............
.............
]
}
You can use Android Studio plugin RoboPOJOGenerator. It is very easy to make model classes from data.
This answer tells how to handle List response in retrofit.
Update
I don't think it is good idea to make custom deserializer just to parse a list. When you can filter or map list after getting response. It will take upto 3-4 lines of code.
If you don't want many classes. then you can safely delete Profile.java and Loc.java in your case, Gson will parse only data that you have declared in your pojo.
Make generic response class
You can make single class for handling all response by using Java Generics. See example.
public class ApiResponse<T> {
#SerializedName("error")
#Expose
private String error;
#SerializedName("success")
#Expose
private boolean success;
#SerializedName("response")
#Expose
private T response;
// make getter setters
}
Now in ApiService you can define response type. Like this
#GET("api/users/login")
Call<ApiResponse<ModelUser>> getUser();
#GET("api/users/login")
Call<ApiResponse<ModelCity>> getCity();
Thus you don't have to create 3 classes every time. This generic class will do work for all response.
Similarly to this question, I would like to convert an object (actually, it is a API response from retrofit) to a json string, so it would be simpler to store it somewhere.
The response structure is something like these:
{
"metadata": {
"count": 0,
"type": "string"
},
"results": [
{
"obj1": {
"param1": "s1",
"param2": "s2"
},
"obj2": {
"param3": 0,
"param4": 0,
"param5": 0
},
"obj3": 0,
"obj4": "27/12/2017"
}
]
}
Using retrofit2, I have the results array stored in a List<MyResponse.Result> and that's the parameter I'm passing to Gson().toJson, like so:
var contentResponse: String = ""
try{
this.contentResponse.plus(Gson().toJson(response))
} catch (e: Exception){
Log.e("Gson error", e.toString())
}
Unfortunately, I'm getting no exception but my contentResponse keeps empty. I`ve tried to use the method in the question mentioned above, but got the same outcome. Any advises?
PS: If there is an easier way to get the retrofit response in a String, it could help as well.
Strings are immutable in JVM. Calling
this.contentResponse.plus(Gson().toJson(response))
is equivalent to
this.contentResponse + (Gson().toJson(response))
This way you can see better that you are not assiging the result to anything. Change it to
this.contentResponse = this.contentResponse.plus(Gson().toJson(response))
This is my Json Data
[
{
"id": 1,
"name": "ANDY",
"Game": {
"car": "1 Item",
"plane": "1 Item"
},
"location": {
"home": 5.555,
"office": 150.316
}
}
}
Here is my calling API :
#GET("/sample.json")
Observable<Response> getAppTours(#Header("If-None-Match") String etag);
How can I access the game and get the car & plane location : home & office ?
I am using Retrofit and Ormlite. I keep getting error on Game and Location while I am adding the #DATABASEFIELD Game game;
The error is said :
Attempt to invoke interface method 'com.j256.ormlite.stmt.QueryBuilder com.j256.ormlite.dao.Dao.queryBuilder()' on a null object reference
It's simple to to get Nested objects if you use core class library to read Json. But Using retrofit you have to create another POJO (model) for game object. Have a brief look.
Copy/paste this JSON as input of this page http://www.jsonschema2pojo.org/:
[
{
"id": 1,
"name": "ANDY",
"Game": {
"car": "1 Item",
"plane": "1 Item"
},
"location": {
"home": 5.555,
"office": 150.316
}
}
]
You had a } instead of a ] to close it up, on biggies. Now change the source type to JSON and select your converter accordingly.
You can check the Retrofit's available converters in their main documentation almost at the bottom of the page http://square.github.io/retrofit/.
Make sure you add the needed line to your gradle, being that something similar to:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Now from jsonschema2pojo click on "Preview" or "Zip" you will then be able to download your new model. Copy those classes to your project.
Your new Retrofit interface will look something like:
public interface YourService {
#GET("your/url")
Call<Yourclass> listStuff();
}
Then, after that, you will be able to make the HTTP request with:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create()) //Converter
.build();
// Create an instance of our GitHub API interface.
YourService service = retrofit.create(YourService.class);
// Create a call instance for looking up Retrofit contributors.
Call<Yourclass> call = service.listStuff();
And get your games and locations with:
Yourclass myclass = call.execute().body();
Game game = myclass.getGame();
Location location = myclass.getLocation();
Check these if you want to get more details:
https://github.com/square/retrofit/blob/master/samples/src/main/java/com/example/retrofit/SimpleService.java
http://square.github.io/retrofit/
https://github.com/joelittlejohn/jsonschema2pojo