Jackson Data Binding for LocalDate[] using annotation - java

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"
]
}

Related

Error thrown when deserialising 2D Json Array using Jackson

I am trying to deserialize a two things from JSON. The format of the first one is as follows:
String json = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
This is the second:
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]
My code is simple, and is as follows:
ObjectMapper mapper = new ObjectMapper();
var asArray = mapper.readValue(json, NoFlyZone[].class);
var asArray2 = mapper.readValue(json, LngLat.class);
The NoFlyZone class:
record NoFlyZone(LngLat[] coordinates) {
#JsonIgnoreProperties("name")
NoFlyZone (#JsonProperty("coordinates") double[][] coordinates) {
this(doubleArrayToLngLatArray(coordinates));
}
private static LngLat[] doubleArrayToLngLatArray(double[][] coordinates) {
var coordinateArray = new LngLat[coordinates.length];
for (int i = 0; i < coordinates.length; i++) {
coordinateArray[i] = new LngLat(coordinates[i][0], coordinates[i][1]);
}
System.out.println(coordinateArray);
return coordinateArray;
}
}
And finally, the LngLat class:
record LngLat(double lng, double lat) {
LngLat (#JsonProperty("longitude") double lng,
#JsonProperty("latitude") double lat) {
this.lng = lng;
this.lat = lat;
}
}
I have tried deserialising them in the way shown above, but a MismatchedInputException is thrown when trying to deserialize the first string, with the error message "Cannot deserialize value of type uk.ac.ed.inf.LngLat from Array value (token JsonToken.START_ARRAY)...". I'm not sure why this is happening, so any help would be appreciated.
I have also tried adding the annotation
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
and fixing it as detailed in Alexander's answer, but then the second string throws an error when attempting to be deserialised.
Since your record LngLat is represented as JSON-array (like "[-3.1, 55.4]") you need to customize its deserialization.
And for that you can use #JsonFormat annotation providing the attribute shape with the value of JsonFormat.Shape.ARRAY. That would instruct Jackson to populate the record properties from the array in order of their declaration.
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
record LngLat(double lng, double lat) {}
And enclosing record NoFlyZone would be simplified to (special method for parsing array of LngLat is redundant):
#JsonIgnoreProperties("name")
record NoFlyZone(LngLat[] coordinates) {}
Usage example:
String json = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, new TypeReference<List<NoFlyZone>>() {}));
Output:
note: toString() method of the NoFlyZone has been overridden to display the array correctly
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
Update
If you need to support the two entirely different structures of JSON, then you also need to customize deserialization on the NoFlyZone level (because its JSON-shape differs).
One of the ways to do that is to introduce the factory method annotated with #JsonCreator. It would expect a single argument of type Map<String, JsonNode> in order to be able to funnel all the properties thought it.
We also need to set the attribute of ignoreUnknown of #JsonIgnoreProperties to true.
Note: the definition of LngLat remains the same (as shown above annotated with #JsonFormat).
#JsonIgnoreProperties(ignoreUnknown = true)
public record NoFlyZone(LngLat[] coordinates) {
#JsonCreator
public static NoFlyZone getInstance(Map<String, JsonNode> fields) throws IOException {
boolean isArray = fields.containsKey("coordinates");
LngLat[] longLat;
if (isArray) {
ObjectReader reader = new ObjectMapper().readerFor(LngLat[].class);
longLat = reader.readValue(fields.get("coordinates")); // parsing JsonNode which corresponds to "coordinates" property
} else {
longLat = new LngLat[] { // creating a single-element array
new LngLat(
Double.parseDouble(fields.get("longitude").toString()),
Double.parseDouble(fields.get("latitude").toString())
)
};
}
return new NoFlyZone(longLat);
}
// toString()
}
Usage example:
String json1 = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json1, new TypeReference<List<NoFlyZone>>() {}));
System.out.println(mapper.readValue(json2, new TypeReference<List<NoFlyZone>>() {}));
Output:
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.0]]}]

JSON Java 8 LocalDateTime format using Jackson in Vert.x

I am trying to create json object from LocaLDateTime but for some reason it is creating json like so, look for issueAt and expireAt key
json {"userID":0,"deviceID":0,"refreshToken":"93180548-23b3-4d1b-8b5b-a105b7cff7f9","issuedAt":{"year":2021,"monthValue":10,"dayOfMonth":27,"hour":9,"minute":22,"second":31,"nano":0,"month":"OCTOBER","dayOfWeek":"WEDNESDAY","dayOfYear":300,"chronology":{"id":"ISO","calendarType":"iso8601"}},"expiresAt":{"year":2021,"monthValue":10,"dayOfMonth":28,"hour":9,"minute":22,"second":31,"nano":0,"month":"OCTOBER","dayOfWeek":"THURSDAY","dayOfYear":301,"chronology":{"id":"ISO","calendarType":"iso8601"}}}
I want it to be like so
batch: [0,0,29a1bf70-648e-4cb5-aef8-5377cf702875,2021-10-26T12:36:10,2021-10-27T12:36:10] .
My code for creating the 2 dates is below
String randomString = UUID.randomUUID().toString();
Instant myInstant1 = Instant.now().truncatedTo(ChronoUnit.SECONDS);
LocalDateTime issuedAt = LocalDateTime.ofInstant(myInstant1, ZoneId.systemDefault());
System.out.println("issued_at : " + issuedAt);
LocalDateTime expiresAt = issuedAt.plusDays(1);
System.out.println("expires_at: " + expiresAt.plusDays(1));
In the below code is where I get the error when I try to use mapto to add the json object to my class object.
JsonObject json = new JsonObject()
.put("userID", userID)
.put("deviceID", deviceID)
.put("refreshToken", randomString)
.put("issuedAt", issuedAt)
.put("expiresAt", expiresAt);
LOG.info("json {}", json.encode());
RefreshToken refreshTokenObj = json.mapTo(RefreshToken.class); //here I am trying to mapTo my class and I get the error
LOG.info("refreshTokenObj {}", refreshTokenObj);
The error I get is
2021-10-27 09:22:31.133+0330 [vert.x-eventloop-thread-1] ERROR com.galiy.main.MainVerticle - Unhandled:
java.lang.IllegalArgumentException: Cannot construct instance of java.time.LocalDateTime (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.galiy.security.refreshToken.RefreshToken["issuedAt"])
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4236) ~[jackson-databind-2.11.4.jar:2.11.4]
and my RefreshToken model is like so,
public class RefreshToken {
private Integer id;
private Integer userID;
private Integer deviceID;
private String refreshToken;
private LocalDateTime issuedAt;
private LocalDateTime expiresAt;
I am not familiar with Vert.x. But according to our discussion under the post, I simply add following 2 line of code before mapTo() and got no error.
ObjectMapper objectMapper = DatabindCodec.mapper();
objectMapper.registerModule(new JavaTimeModule());
Console output:
RefreshToken{id=null, userID=0, deviceID=0, refreshToken='9da220ce-bc66-4561-b924-988c7f394f2d', issuedAt=2021-10-27T17:21:28, expiresAt=2021-10-28T17:21:28}
And in my experience, you can also configure ObjectMapper to handle the output format of LocalDateTime as you want while serialization as follows:
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
You will need to annotate your LocalDateTime member as follows:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
LocalDateTime myTime
Here is the link to full answer that explains all the details: Spring Data JPA - ZonedDateTime format for json serialization

Parsing JSON with java : dynamic keys

I need to create a JSON response with some dynamic fields in java. Here is an example of the JSON response I want to return :
{
"success": true,
"completed_at": 1400515821,
"<uuid>": {
type: "my_type",
...
},
"<uuid>": {
type: "my_type",
...
}
}
The "success" and the "completed_at" fields are easy to format. How can I format the fields? What would be the corresponding java object?
Basically I want to work with 2 java objects :
public class ApiResponseDTO {
private boolean success;
private DateTime completedAt;
...
}
and
public class AuthenticateResponseDTO extends ApiResponseDTO {
public List<ApplianceResponseDTO> uuids = new ArrayList<ApplianceResponseDTO>();
}
These java objects don't correspond to the expected JSON format. It would work if I could change the JSON format to have a list, but I can't change it.
Thanks a lot!
You can massage your data into JSON form using the javax.json library, specifically the JsonObjectBuilder and the JsonArrayBuilder. You'll probably want to nest a few levels of a toJson() method which will either give you the string representation you're looking for, or the JsonObject/JsonArray you desire. Something like this:
JsonArray value = null;
JsonArrayBuilder builder = Json.createArrayBuilder();
for (ApplianceResponseDTO apr : uuids) {
builder.add(apr.toJson());
}
value = builder.build();
return value;

How to convert List to a JSON Object using GSON?

I have a List which I need to convert into JSON Object using GSON. My JSON Object has JSON Array in it.
public class DataResponse {
private List<ClientResponse> apps;
// getters and setters
public static class ClientResponse {
private double mean;
private double deviation;
private int code;
private String pack;
private int version;
// getters and setters
}
}
Below is my code in which I need to convert my List to JSON Object which has JSON Array in it -
public void marshal(Object response) {
List<DataResponse.ClientResponse> clientResponse = ((DataResponse) response).getClientResponse();
// now how do I convert clientResponse list to JSON Object which has JSON Array in it using GSON?
// String jsonObject = ??
}
As of now, I only have two items in List - So I need my JSON Object like this -
{
"apps":[
{
"mean":1.2,
"deviation":1.3
"code":100,
"pack":"hello",
"version":1
},
{
"mean":1.5,
"deviation":1.1
"code":200,
"pack":"world",
"version":2
}
]
}
What is the best way to do this?
There is a sample from google gson documentation on how to actually convert the list to json string:
Type listType = new TypeToken<List<String>>() {}.getType();
List<String> target = new LinkedList<String>();
target.add("blah");
Gson gson = new Gson();
String json = gson.toJson(target, listType);
List<String> target2 = gson.fromJson(json, listType);
You need to set the type of list in toJson method and pass the list object to convert it to json string or vice versa.
If response in your marshal method is a DataResponse, then that's what you should be serializing.
Gson gson = new Gson();
gson.toJson(response);
That will give you the JSON output you are looking for.
Assuming you also want to get json in format
{
"apps": [
{
"mean": 1.2,
"deviation": 1.3,
"code": 100,
"pack": "hello",
"version": 1
},
{
"mean": 1.5,
"deviation": 1.1,
"code": 200,
"pack": "world",
"version": 2
}
]
}
instead of
{"apps":[{"mean":1.2,"deviation":1.3,"code":100,"pack":"hello","version":1},{"mean":1.5,"deviation":1.1,"code":200,"pack":"world","version":2}]}
you can use pretty printing. To do so use
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(dataResponse);
Make sure to convert your collection to Array:
Gson().toJson(objectsList.toTypedArray(), Array<CustomObject>::class.java)
We can also use another workaround by first creating an array of myObject then convert them into list.
final Optional<List<MyObject>> sortInput = Optional.ofNullable(jsonArgument)
.map(jsonArgument -> GSON.toJson(jsonArgument, ArrayList.class))
.map(gson -> GSON.fromJson(gson, MyObject[].class))
.map(myObjectArray -> Arrays.asList(myObjectArray));
Benifits:
we are not using reflection here. :)

Jackson Object mapping approach with Linkedin Rest API

When dealing with Linkedin Rest API, a lot of the fields has format like this:
"positions":
{
"_total": 1,
"values": [{"title": "Software Developer"}]
}
instead of:
"positions":
{
[{"title": "Software Developer"}]
}
This causes a lot of trouble when I try to map the json to a Position object. I am using Java with Jackson to parse the JSON response. Is there a way to set up object mapper so that it would automatically ignore the "_total" and "values" field?
I think it is not possible to configure ObjectMapper to do this automatically.
You could try writing your own parser, something along these lines:
JsonFactory f = new JsonFactory();
JsonParser jp = f.createJsonParser(new File("positions.json"));
List<Position> positions = new LinkedList<Position>();
jp.nextToken(); // will return JsonToken.START_OBJECT (verify?)
while (jp.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jp.getCurrentName();
jp.nextToken(); // move to value, or START_OBJECT/START_ARRAY
if ("positions".equals(fieldname)) { // contains an object
Position pos = new Position();
while (jp.nextToken() != JsonToken.END_OBJECT) {
String namefield = jp.getCurrentName();
jp.nextToken(); // move to value
if ("value".equals(namefield)) {
pos.setValue(jp.getText());
}
}
}
jp.close();
Obviously #kpentchev provided a viable solution to this issue, but I personally tend to avoid manual parser as much as possible. In this case, I ended up writing a sort of wrapper class to map the raw json:
public class PositionWrapper
{
private Long _total;
private List<Position> values;
//setter and getter
}
Although it's a bit redundant this way, but it avoids going with a manual wrapper. Works well for me, even for nested objects.

Categories

Resources