Gson is unable to serialize LocalDateTime data in Android (Java) - java

[UPDATED:02.20.2023] I was misused between LocalDateTime and Instant data type in my example so I adjusted it. Sorry for this inconvenient. Thank you
I am practicing coding and facing an issue relating to Gson and LocalDateTime in Java Android.
For example, I created a Java object
public class Person {
private String name;
private LocalDateTime createdAt;
// Constructors
// Getters and Setters
}
Then I used Gson to convert this Person object in to Json
Person person = new Person("John Doe", LocalDateTime.now()); //For example: 2022-02-02T11:11:045701100
String personJsonAsString = new Gson().toJson(person);
I expect the Json should be
{
"name": "John Doe",
"createdAt": "2022-02-02T11:11:045701100"
}
But the result I got was
{
"name": "John Doe",
"createdAt": {} // <- It's empty here
}
After that, I researched and found that I can add custom serializer class into GsonBuilder instance so I tried again
Person person = new Person("John Doe", LocalDateTime.now()); //For example: 2022-02-02T11:11:510Z
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeJsonSerializer()).create();
String personJsonAsString = gson.toJson(person);
While LocalDateTimeJsonSerializer is a class that I wrote
public class LocalDateTimeJsonSerializer implements JsonSerializer<LocalDateTime> {
#Override
public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
}
The exception was throw when I try to persist the data on the Android app like this:
For now, I cannot figure out what makes LocalDateTime cannot be serialized in Android Platform by Gson. Please help me. Thank you so much for your time.
What I did try:
Researching other questions on Stackoverflow.
Try out any available solutions.
What I was expecting:
I was expecting to know the actual root cause of the exception instead of fixing the bug and making it run only

Square peg in a round hole
Wrong class. Use Instant, not LocalDateTime.
public class Person {
private String name;
private Instant createdAt;
…
}
Your input "2022-02-02T11:11:510Z" has a Z on the end. That letter indicates an offset from UTC of zero hours-minutes-seconds.
Instant instant = Instant.parse( "2022-02-02T11:11:510Z" ) ;
The LocalDateTime lacks any concept of offset or time zone. So that class cannot represent a moment, is not a specific point on the timeline. That class cannot represent the meaning intended by your input. That class cannot be used to record the moment something was created.
Your input string complies with ISO 8601 standard. See Wikipedia.
You showed line of code:
Person person = new Person("John Doe", LocalDateTime.now());
I cannot imagine a case where calling LocalDateTime.now is the right thing to do.
Change to:
Person person = new Person( "John Doe" , Instant.now() ) ;
Example code
I am no expert on Gson. But I seem to have gotten the following to work.
For brevity, let's define your Person class as a record. I am pleased to see that the current version 2.10.1 of Gson seems to be working well with records.
package work.basil.example.gson;
import java.time.Instant;
public record Person( String name , Instant createdAt ) { }
Apparently the only date-time type adapter bundled with GSON is for java.util.Date. That terrible legacy class should be avoided. Use only java.time classes.
We are using java.time.Instant. So we need to write our own type adapter. You can find 3rd party open-source type adapter implementations. But writing your own for Instant is simple enough.
The java.time classes by default use ISO 8601 standard formats when parsing/generating text. So we will use that standard.
package work.basil.example.gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.time.Instant;
public class Gson_InstantTypeAdapter extends TypeAdapter < Instant >
{
#Override
public void write ( JsonWriter jsonWriter , Instant instant ) throws IOException
{
jsonWriter.value( instant.toString() ); // Writes in standard ISO 8601 format.
}
#Override
public Instant read ( JsonReader jsonReader ) throws IOException
{
return Instant.parse( jsonReader.nextString() ); // Parses standard ISO 8601 format.
}
}
Now we write an app to exercise these classes.
First, define a Gson object to be using for generating and parsing JSON text.
Gson gson =
new GsonBuilder()
.registerTypeAdapter( Instant.class , new Gson_InstantTypeAdapter() )
.create();
Write some JSON.
// Write to JSON.
Person person = new Person( "John Doe" , Instant.now() ); //For example: 2022-02-02T11:11:510Z
String personAsJson = gson.toJson( person );
personAsJson = {"name":"John Doe","createdAt":"2023-02-19T22:47:42.566132Z"}
And read some JSON.
// Parse JSON.
Person p = gson.fromJson( personAsJson , Person.class );
p.toString() = Person[name=John Doe, createdAt=2023-02-19T22:47:42.566132Z]
Bring that code together.
package work.basil.example.gson;
import com.google.gson.*;
import java.time.Instant;
public class App
{
public static void main ( String[] args )
{
App app = new App();
app.demo();
}
private void demo ( )
{
Gson gson =
new GsonBuilder()
.registerTypeAdapter( Instant.class , new Gson_InstantTypeAdapter() )
.create();
// Write to JSON.
Person person = new Person( "John Doe" , Instant.now() ); //For example: 2022-02-02T11:11:510Z
String personAsJson = gson.toJson( person );
System.out.println( "personAsJson = " + personAsJson );
// Parse JSON.
Person p = gson.fromJson( personAsJson , Person.class );
System.out.println( "p.toString() = " + p );
}
}

Related

How to extract date from JSON with year, day and month as separate fields in format yyyy-mm-dd

I am new to JSON, to get transaction details of user i am making API call to one of the webservice in the JSON response from the API the transaction date is coming as below
{
"trasanction_date": {
"year": 2021,
"month": 6,
"day": 16
}
}
I need to convert the above date in format yyyy-mm-dd to insert it into Cassandra. Right now how i am converting is, i am creating new JSONObject from above String like
JSONObject trasaction = new JSONObject("{\"trasanction_date\":{\"year\":2021,\"month\":6,\"day\":16}}");
JSONObject date = trasaction.get("trasaction_date");
String year = date.getString("year");
String month = date.getString("month");
String day = date.getString("day");
//concatenating the final result to frame the date
String transactionDate = year+month+Day
Is there a way to efficiently convert the above JSON in format yyyy-mm-dd with out extracting and concatenating the string. Please help with above thanks in advance.
Create TransactionDate.java class witch contains three String fields year, month,day with getters and setters. Then, createTransactionDetails.java class witch have property of type TransactionDate and use gson library to convert JSON string to java object.
You can take a look at this article to see how to use gson
Then inside TransactionDate.java class you can override toString() method to something like this :
#Override
String toString() {
return this.year + " " + this.month + " " + this.day;
}
Finally, instad of returning transactionDate string you can get TransactionDate object from TransactionDetails and return it's String representation.

Object conversion to Json String and conversion back to object failing with missing DateTime element Android

I am trying to save an object into shared preferences and then retrieve.
The object consists of single DateTime for the current day, and a List of objects for the biggest stock losers (stock symbols as a string, and percentage change as a double).
The object model is:
public class DayLosersSharedPreference {
LocalDateTime dateTime;
List<PercentageChange> percentageChangesList;
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
public List<PercentageChange> getPercentageChangesList() {
return percentageChangesList;
}
public void setPercentageChangesList(List<PercentageChange> percentageChangesList) {
this.percentageChangesList = percentageChangesList;
}
}
The single object (containing DateTime and object List) is passed to my shared preferences class for storage like this:
public class SharedPreferences {
Context context = MySuperAppApplication.getContext();
android.content.SharedPreferences sharedPreferences = context.getSharedPreferences(STRINGS.LOSERS_SP(), Context.MODE_PRIVATE);
Gson gson = new Gson();
public void storeLosers(DayLosersSharedPreference losers) {
System.out.println("Object being stored date: " + losers.getDateTime());
System.out.println("Object being stored array: " + losers.getPercentageChangesList());
String dailyLosersJson = gson.toJson(losers);
System.out.println("Object being stored as Json String: " + dailyLosersJson);
sharedPreferences.edit()
.putString(STRINGS.DAILY_LOSERS_SP(), dailyLosersJson)
.apply();
}
}
The output of the println()'s shows the object before the Json String conversion is fine:
I/System.out: Object being stored date: 2019-03-18T00:00
I/System.out: Object being stored array: [com.roboticc.alpha.Models.PercentageChange#8bef7ab, com.roboticc.alpha.Models.PercentageChange#207be08, com.roboticc.alpha.Models.PercentageChange#b6289a1, com.roboticc.alpha.Models.PercentageChange#269d7c6, com.roboticc.alpha.Models.PercentageChange#ff36387, com.roboticc.alpha.Models.PercentageChange#45eb2b4, com.roboticc.alpha.Models.PercentageChange#1fd1edd, com.roboticc.alpha.Models.PercentageChange#41baa52, com.roboticc.alpha.Models.PercentageChange#b18b123, com.roboticc.alpha.Models.PercentageChange#38e4620]
But there is a problem in how I am converting this to a Json string. When I look at the output after it was converted it loses the DateTime element:
I/System.out: Object being stored as Json String: {"dateTime":{},"percentageChangesList":[{"percentageChange":-0.15908367801463796,"symbol":"AAL"},{"percentageChange":0.0,"symbol":"AAME"},{"percentageChange":0.19011406844106543,"symbol":"AABA"},{"percentageChange":0.500000000000002,"symbol":"AAOI"},{"percentageChange":0.6455517450070613,"symbol":"AAWW"},{"percentageChange":0.8957770510450668,"symbol":"AAXJ"},{"percentageChange":1.0208467655276197,"symbol":"AAPL"},{"percentageChange":1.081223628691974,"symbol":"ABCB"},{"percentageChange":2.0748867159551576,"symbol":"AAON"},{"percentageChange":4.29524603836531,"symbol":"AAXN"}]}
Once I retrieve it from shared preferences, predictably I get a null pointer on the date time as it is not being saved but can access the percentage change list.
I assume it is something wrong with how I am converting from object to Json string, do I have to do something like pass the model type? or can DateTime not be stored in Json in that format?
Thanks for your help
EDIT: I was able to change the DateTime to a string and the convert back after retrieval to fix this. I am still interested why this doesn't work directly without DateTime conversion. Especially considering a whole List of objects can be passed.
gson is not aware of LocalDateTime objects and hence fails to parse it. If you do want it to directly parse LocalDateTime without having to convert it to a String first you will have to tell gson HOW to parse LocalDateTime using registerTypeAdaptor.
Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
#Override
public LocalDateTime deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
Instant instant = Instant.ofEpochMilli(json.getAsJsonPrimitive().getAsLong());
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
}).create();
This is something that the newer versions of GSON might be able to handle though: https://github.com/google/gson/pull/1266/files
I'm not sure if the above commit has been published into any of their releases, but it sure does look like some duture version will be able to handle this automatically.

Jackson Data Binding for LocalDate[] using annotation

I am converting a JSON file into Java object using Jackson with Java 8 Module. But while converting JSON array to LocalDate[] application is throwing an exception.
How to convert below JSON array to LocalDate[] using annotations?
JSON
{
"skip": [
"01/01/2019",
"26/01/2019"
]
}
Code
#JsonFormat(pattern = "dd/MM/yyyy")
#JsonSerialize(using = LocalDateSerializer.class)
#JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate[] skip;
Exception
com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_STRING) within Array, expected VALUE_NUMBER_INT
at [Source: (ByteArrayInputStream); line: 25, column: 3] (through reference chain: com.saalamsaifi.springwfrlroster.model.Team["skip"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
at com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer.deserialize(LocalDateDeserializer.java:110)
at com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer.deserialize(LocalDateDeserializer.java:38)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3070)
Since skip is of type array, LocalDateSerializer, LocalDateDeserializer and JsonFormat do not work out of the box - they are implemented to expect direct value tokens and not arrays.
You can implement your own serializer/deserializers. A naive deserializer I implemented to deserialize your example is the following:
public class CustomLocalDateArrayDeserializer extends JsonDeserializer<LocalDate[]> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
#Override
public LocalDate[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ArrayList<LocalDate> list = new ArrayList<>();
JsonToken currentToken = p.getCurrentToken();
if (currentToken != JsonToken.START_ARRAY) {
throw new JsonMappingException(p, "Not an array!");
}
currentToken = p.nextToken();
while (currentToken != JsonToken.END_ARRAY) {
if (currentToken != JsonToken.VALUE_STRING) {
throw new JsonMappingException(p, "Not a string element!");
}
LocalDate localDate = LocalDate.parse(p.getValueAsString(), formatter);
list.add(localDate);
currentToken = p.nextToken();
}
return list.toArray(new LocalDate[0]);
}
}
And I changed the field annotation to be #JsonDeserialize(using = CustomLocalDateArrayDeserializer.class).
You can work on it to iterate & improve over it, make it read&respect #JsonFormat annotation and so on, if you think it is worth the effort.
I suspect looking at your code and your json model it is trying to convert to an array using a deserializer that is defined for one object. It simple terms you are trying to convert a single item to an array which it cant parse. You could Try a list of LocalDate instead. Something Like:
List<LocalDate> skip;
You might even need to create your own Deserializer based on the date serializer.
Just first glance: are actually json objects in your json array or just Strings as you showed? This should be something like this:
{
"skip": [
"key1":"01/01/2019",
"key2":"26/01/2019"
]
}

(De)serialising LocalDate: how to prepare JSON

I have a class that represent data in which there is LocalDate field.
public class Student {
private String name;
private LocalDate dob;
//getters and setters
}
In pom.xml I have jackson-modules-java8 so that LocalDate could be deserialized. I'm having difficulty preparing correct JSON to be able to send POST. Here it is:
{ "name" : "John", "dob" : [ 1986::10::06 ] }
In a response to POST I get Cannot deserialize instance ofjava.time.LocalDateout of START_ARRAY token. Decorating the field dob with annotation #JsonFormat(pattern = "yyyy::MM::dd") didnn't work. Three years ago workaround was published but things may have changed since then.
From the w3school description of json values:
In JSON, values must be one of the following data types:
a string
a number
an object (JSON object)
an array
a boolean
null
According this definition your json is not valid because [1986::10::06] is not a valid value.
You need to treat it as a string, adding the " characters around it instead of squared brackets.
The right json must be
{ "name" : "John", "dob" : "1986::10::06" }
The LocalDate API doc says:
A date without a time-zone in the ISO-8601 calendar system, such as
2007-12-03.
So you JSON should be like:
{ "name" : "John", "dob" : "1986-10-06" }

Handle JSON response date in array

so I'm inserting the date of birth to an API, and the API returns the updated information, which i'm supposed to present on another page after handling the data in java backend.
Here is what gets returned in JSON:
"firstname": "John",
"middlename": "The",
"lastname": "Doe",
"displayName": "John The Doe",
"dateOfBirth": [
1994,
3,
26
]
so what I'm having trouble with, is picking out the 3 (year/month/day) in separate variables, because if theres no 0 in 03 ( mars for example ) i want to add the 0, same goes with day.
Here i'm getting the object:
#Override
public Object getDateOfBirth() {
return get("dateOfBirth");
}
But i'm getting [1994,3,26] which obviously looks very bad displayed on a website.
How would you get the 3 "1994,3,26" in different variables?
Any help is greatly appreciated.
if you are using Java 8 or later, you can use LocalDate. The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate.of(year, month, day); //2015-12-22
LocalDate.of(Integer.parseInt(dateOfBirth[0]), Integer.parseInt(dateOfBirth[1]), Integer.parseInt(dateOfBirth[2]));
But i'm getting [1994,3,26] which obviously looks very bad displayed on a website.
How would you get the 3 "1994,3,26" in different variables?
You can transform the output of returned array object into custom format for display purpose. Assuming get("dateOfBirth") returns and Array object. You can do something similar ...
String result = Arrays.toString(dateOfBirthArrayObject).replaceAll("[\\[\\]]", "");
System.out.println(result);
Output: 1994, 3, 26
take a look on this link This can help.
I can recommend you to use the Gson
if you use maven add this dependency to the pom
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>
public class MyJsonAsClass
{
private String firstname;
private String middlename;
private String lastname;
private String displayName;
private List<String> dateOfBirth;
public MyJsonAsClass()
{
}
public static MyJsonAsClass fromString(String json)
{
Gson gson = new Gson();
MyJsonAsClass info = gson.fromJson(json, MyJsonAsClass.class);
return info;
}
/*you need to add getters & setters */
public List<String> getdateOfBirth()
{
for (String date : dateOfBirth) {
if (date.length()==1)
date='0'+date;
}
return dateOfBirth;
}

Categories

Resources