I have JSON string which was generated by GSON library and it looks like :
{
"id": 10,
"articleNumber": 5009,
"processDate": {
"year": 2021,
"month": 1,
"day": 1
},
"price": 1.22
}
I want to use Jackson for deserialize the above JSON. But it fails at processDate field due to the format how processDate field is present in the JSON.
How to parse the above JSON string by using some custom deserializer?
It seems you unwillingly got Jackson's built-in
LocalDateDeserializer parsing your date.
This deserializer supports several JSON date formats
(string, integer array, epoch day count)
"2021-1-1"
[2021, 1, 1]
18627
but unfortunately not your object-like format
{ "year": 2021, "month" :1, "day": 1 }
Therefore you need to write your own deserializer for LocalDate.
This is not so hard.
public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = parser.getCodec().readTree(parser);
try {
int year = node.get("year").intValue();
int month = node.get("month").intValue();
int day = node.get("day").intValue();
return LocalDate.of(year, month, day);
} catch (Exception e) {
throw JsonMappingException.from(parser, node.toString(), e);
}
}
}
Then, in your Java class you need to tell Jackson,
that you want its processDate property be deserialized
by your own LocalDateDeserializer.
public class Root {
private int id;
private int articleNumber;
#JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate processDate;
private double price;
// getters and setters (omitted here for brevity)
}
I dont know java that well, just make a custom type like this. below
just create a custom Struct like:
inline class processDate {
int year,
int month,
int day,
public Date getDate(){
DateFormat formatter = new SimpleDateFormat("dd-MMM-yy");
Date date = formatter.parse(this.day + "-" + this.month + "-" + this.year);
return date;
}
}
Related
I have legacy data coming in to my API as part of UserRequest model like
#PostMapping
public MyResponse saveUser(#Valid #RequestBody UserRequest userRequest) {
...
}
UserRequest class uses OffsetDateTime for dateRegistered field:
public class UserRequest {
...
OffsetDateTime birthDate;
...
}
The problem I am having is that the data is coming into the API using below format for dateRegistered field:
{
"last-name":"Mikel",
"birth-date":"20200716"
}
So, the string representation "20200716" from JSON request needs to be somehow converted to OffsetDateTime like "2020-07-16T00:00:00Z" (the time portion and offset is set to 0s).
By default, the "20200716":
{
"last-name":"Mikel",
"birth-date":"20200716"
}
, gets converted to OffsetDateTime value of
{
"last-name": "Mikel",
"birth-date": "1970-08-22T19:18:36Z"
}
, which is obviously way off.
How do I convert a string representation of date in Json field like "20200716" to its OffsetDateTime representation like "2020-07-16T00:00:00Z" or "2020-07-16T00:00:00.000+00:00"?
I was trying to annotate my OffsetDateTime field with #JsonFormat("yyyyMMdd") but that is throwing exception like: JSON parse error: Cannot deserialize value of type java.time.OffsetDateTime from String "20200716".
you don't need a JSON annotation. You need to adjust the setter as follow.
public class MedicalCandidateRequest {
private OffsetDateTime dateRegistered;
public OffsetDateTime getDateRegistered() {
return dateRegistered;
}
public void setDateRegistered(String dateString) {
final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSxx";
DateTimeFormatter dtFormatter = DateTimeFormatter.ofPattern(pattern);
this.dateRegistered = OffsetDateTime.parse(dateString, dtFormatter );
}
}
Change the parameter of the setter method to a String and do the conversion yourself.
public void setDateRegistered(String value) {
this.dateRegistered = doConversionHere(value);
}
Thanks for suggestions but I have decided to go with my own implementation.
I provided a custom deserializer like:
public class CustomDateDeserializer extends JsonDeserializer<OffsetDateTime> {
private static final String PATTERN = "yyyyMMdd";
private final DateTimeFormatter formatter;
public CustomDateDeserializer() {
this.formatter = DateTimeFormatter.ofPattern(PATTERN);
}
#Override
public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException, JacksonException {
LocalDate localDate = LocalDate.parse(parser.getText), formatter);
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDate, LocalTime.MIDNIGHT, ZoneOffset.UTC);
return offsetDateTime;
}
}
, which I then use to annotate my model field like:
#JsonDeserialize(using = CustomDateDeserializer.class)
private OffsetDateTime birthDate;
My pojo class is as given below
public class Employee {
private int empId;
#NotEmpty
private String empName;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date empDoj;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Date getEmpDoj() {
return empDoj;
}
public void setEmpDoj(Date empDoj) {
this.empDoj = empDoj;
}
I am accepting the request in controller as shown below
#RequestMapping(value = "/employee",method = RequestMethod.POST)
public Employee employee(#RequestBody #Valid Employee employee
) {
return employee ;
}
when I am sending the below JSON request from postman it is giving me an error
{
"empName":"Anubhav",
"empDoj":"10/10/2019"
}
JSON parse error: Cannot deserialize value of type java.util.Date from String "10/10/2019": not a valid representation (error: Failed to parse Date value '10/10/2019': Cannot parse date "10/10/2019": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSX", "yyyy-MM-dd'T'HH:mm:ss.SSS", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd")); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.util.Date from String "10/10/2019": not a valid representation (error: Failed to parse Date value '10/10/2019': Cannot parse date "10/10/2019": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSX", "yyyy-MM-dd'T'HH:mm:ss.SSS", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))
at [Source: (PushbackInputStream); l
when I am doing the same for the method handler
#RequestMapping(value = "/dateformat",method = RequestMethod.POST)
public Date dateex(#DateTimeFormat(pattern = "dd-MM-yyyy") #RequestParam(value = "date")Date date) {
}
It is working fine. Converting string specified in given pattern to Date object then why not with Java class member. In the docs also it says it can be used either at method parameter or field formatted as given date type of field
Since you are sending in JSON you need to add the #JsonFormat(pattern="dd/MM/yyyy") annotation to empDoj. If all your dates will be this format you can set spring.jackson.date-format=dd/MM/yyyy in your application.properties file.
Some details about my vote to close.
You are attempting to use a SpringBoot parameter annotation to specify JSON formatting.
I believe you need to use the JsonFormat annotation
(as noted in the buræquete answer to this question)
i have the following scenario:
some times i get this JSON Object
{"id": 1, "cp": "male", "money": 10.0, "startDate": "07:00", "endDate": "10:00"}
and others this one
{"id":1, "cp": "male", "money": 10.0, "startDate": 21600, "endDate": 32400}
as you can se the JSON has the same fields but some times the startDate and endDate are in diferent formats
in seconds in the second case.
y have simple java Class
public class Person {
private Integer id;
private String cp;
private Double money;
#JsonDeserialize(converter = StringToLocalTimeConverter.class)
private LocalTime startDate;
#JsonDeserialize(converter = StringToLocalTimeConverter.class)
private LocalTime startDate;
//Getter-Setter
Converter
public class StringToLocalTimeConverter extends StdConverter<String, LocalTime> {
static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME;
#Override
public LocalTime convert(String value) {
Long number = Long.parseLong(value);
return LocalTime.MIN.plusSeconds(number);
}
}
Obviously I'm getting a parse exception because I can't map both data values into the same LocalTime due to format.
Is it possible to map both JSON objects to the same Class? how can I workaround this or do I have to create separate Classes
what solution can be applied.
Well , you can simply try parsing it with DateTimeFormatter.ISO_LOCAL_TIME first. If it fails , then you parse it as a long :
public class StringToLocalTimeConverter extends StdConverter<String, LocalTime> {
static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME;
#Override
public LocalTime convert(String value) {
try {
return LocalTime.parse(value, DATE_FORMATTER);
} catch (DateTimeParseException e) {}
Long number = Long.parseLong(value);
return LocalTime.MIN.plusSeconds(number);
}
}
Json mapper converts LocalDate to month, year, day of month ... when converting java class to json like this,
"dob":{
"year": 1992,
"month": "MARCH",
"dayOfMonth": 19,
"dayOfWeek": "THURSDAY",
"era": "CE",
"dayOfYear": 79,
"leapYear": true,
"monthValue": 3,
"chronology": {
"calendarType": "iso8601",
"id": "ISO"
}
}
this is saved as a Date in mysql as 1992-03-19 how to return this date as it is like
"dob:1992-03-19"
Jackson and java.time types
The Jackson JavaTimeModule is used to handle java.time serialization and deserialization.
It provides a set of serializers and deserializers for the java.time types. If the SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is disabled, java.time types will be serialized in standard ISO-8601 string representations.
Handling serialization in your particular format
However, once you have a very particular format, you can create a custom serializer:
public class DateOfBirthSerializer extends JsonSerializer<LocalDate> {
#Override
public void serialize(LocalDate value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeString("dob:" + value.format(DateTimeFormatter.ISO_DATE));
}
}
Then you can use it as follows:
public class Foo {
#JsonSerialize(using = DateOfBirthSerializer.class)
private LocalDate dateOfBirth;
// Getters and setters
}
Alternatively you can use:
SimpleModule module = new SimpleModule();
module.addSerializer(LocalDate.class, new DateOfBirthSerializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
It will be applied to all LocalDate instances serialized with that ObjectMapper.
Handling deserialization in your particular format
For deserialization, you can use something like:
public class DateOfBirthDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser p,
DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
if (value.startsWith("dob:")) {
value = value.substring(4);
} else {
throw ctxt.weirdStringException(value,
LocalDate.class, "Value doesn't start with \"dob:\"");
}
return LocalDate.parse(value, DateTimeFormatter.ISO_DATE);
}
}
I am trying to deserialize this type of json
{
"_embedded": {
"list": [
{
"000000": {
"date": "2015-07-10 14:29:15"
}
},
{
"111111": {
"date": "2015-07-11 14:29:15"
}
}
]
}
}
I manage to get a list inside my embedded object but the list entries are empty.
My Embedded class looks like this
public class Embedded {
#SerializedName("list")
private List<ListEntry> entries;
}
But I am not able to deserialize the list's entries. I think the problem is the fact that the map is nested inside an object that doesn't have a name.
public class ListEntry {
private Map<String, ListEntryInfo> map;
}
The initial problem is the way you declared your hierarchy. A ListEntry is a Map<String, ListEntryInfo> but does not have a Map<String, ListEntryInfo>. To make it work you have three options:
declare the ListEntry class as class ListEntry extends HashMap<String, ListEntryInfo> {}, which is a bad idea in my opinion
get rid of the ListEntry class and declare the entries list like this #SerializedName("list") List<Map<String, ListEntryInfo>> entries;
use the approach I initially described below, by implementing a custom deserializer
As said before what you could do is to write a custom deserializer, so that you have a more typed list of entries.
As a ListEntry instance has only one ListEntryInfo value mapped to a key, I would change the structure to this:
class ListEntry {
private String key;
private ListEntryInfo value;
public ListEntry(String key, ListEntryInfo value) {
this.key = key;
this.value = value;
}
public String toString() {
return key + " -> " + value;
}
}
class ListEntryInfo {
//assuming we store the date as a String for simplicity
#SerializedName("date")
private String date;
public ListEntryInfo(String date) {
this.date = date;
}
public String toString() {
return date;
}
}
Now you need to write a deserializer to create a new ListEntry instance when you deserialize the JSON:
class ListEntryDeserializer implements JsonDeserializer<ListEntry> {
#Override
public ListEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Iterator<Map.Entry<String, JsonElement>> ite = json.getAsJsonObject().entrySet().iterator();
//you may want to throw a custom exception or return an "empty" instance there
Map.Entry<String, JsonElement> entry = ite.next();
return new ListEntry(entry.getKey(), context.deserialize(entry.getValue(), ListEntryInfo.class));
}
}
This deserializer will read each ListEntry. As it's composed of a single key-value pair (in the first case the String "000000" is mapped to one ListEntryInfo and so on), we fetch the key and deserialize the corresponding ListEntryInfo with JsonDeserializationContext instance.
The final step, is to register it within the parser:
Gson gson = new GsonBuilder().registerTypeAdapter(ListEntry.class, new ListEntryDeserializer()).create();
Running it on your example, you should get:
[000000 -> 2015-07-10 14:29:15, 111111 -> 2015-07-11 14:29:15]