Jackson custom date formatter - json

I want to serialize a Json object with a date field, into a Pojo.
For example
{
date: <some arbitrary date format>
}
to
#JsonFormat(???)
LocalDateTime dateTime;
The problem is that the date can be in any number of formats. For example, maybe just a year, just a date, or date and time. And even those can be in multiple formats.
2021
2021-5-23
2021/05/23
2021/05/23 02:07
2021-05-23 02:07:53
2021/05/23 02:07:50.567
I've already written a parser that has a list of formats and will loop through them until it finds one that works. How can I write a custom Jackson serializer or formatter that uses that code and can take a string from a Json object and properly convert it to a Date or LocalDateTime object in the Pojo?
If it makes a difference, I'm also doing this in SpringBoot

I think, you are talking about DEserializing? If so, you can create your own JsonDeserializer that supports LocalDateTime and register it with JsonComponent. This would look like this:
(The "input" can be accessed via p.getText())
#JsonComponent
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String stringValue = p.getText();
// Use your parser instead of this
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("yyyy[-MM-dd HH:mm:ss]")
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).toFormatter();
return LocalDateTime.parse(stringValue, formatter);
}
}

Related

SpringBoot, Hibernate and REST - date format in JSON

I have a problem with date format in JSON response generated in REST project (SpringBoot+Hibernate).
When I call function I got JSON like this:
"rezerwacjaDataOd": 1535580000000,
"rezerwacjaDataDo": 1535839200000,
"rezerwacjaGodzOd": "14:00:00",
"rezerwacjaGodzDo": "12:00:00"
my entity:
private Date rezerwacjaDataOd;
private Date rezerwacjaDataDo;
private Time rezerwacjaGodzOd;
private Time rezerwacjaGodzDo;
It's Date from java.sql and Time from java.sql too
My controller:
#RestController
#CrossOrigin
#RequestMapping("api/rezerwacja")
#Api
public class RezerwacjaController {
...
#GetMapping(value = "/getRezerwacjaById")
public #ResponseBody
Rezerwacja getRezerwacjaById(Integer id) {
return rezDao.findOne(id);
}
...
Why Time is in "12:00:00" format, but Date in 1535580000000 format?
How to make Date to be in "yyyy-MM-dd" format?
You should do two things
add spring.jackson.serialization.write-dates-as-timestamps:false in your application.properties this will disable converting dates to timestamps and instead use a ISO-8601 compliant format
You can then customize the format by annotating the getter method of you dateOfBirth property with #JsonFormat(pattern="yyyy-MM-dd")
The differences in the way hibernate persists the date/time objects in the database have to do with the way these objects are used.
Per the documentation Time is a thin wrapper around Date that allows the underlying JPA provider to save the date object using the convention your noticed.
On the other hand, the Date object you pass in is converted directly to a timestamp and gets saved this way.
In both cases you can retrieve the value in question and serialize over to the desired format (with ISO-8601 being the best).
Another solution, other than the one mentioned above, is to create a custom serializer to do this.
A simple implementation would be:
public class Iso8601Serializer extends StdSerializer<Date> {
private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public Iso8601Serializer() {
this(null);
}
public Iso8601Serializer(Class clazz) {
super(clazz);
}
#Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
if (date == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeString(DateFormatUtils.format(date, ISO_8601_FORMAT));
}
}
}
Also (and this is a personal thing), I would advise in using plain Date objects to store dates and futhermore, have the respective fields annotated as #Temporal.

Can not convert JSON to domain object with spring restTemplate

Actually I try to invoke a get request with the restTemplate in Spring. Debbuging my application clearly shows that the JSON is downloaded but the automatic mapping does not work. My List of domain object includes only 0 values and null values.
When I invoke the get request from the browser, I get the following response as JSON (I copied here the first two record out of the 3 192):
[{"OrderId":77862,"DateAdded":"2016-04-30T02:25:40.263","OrderItemCorpusId":"HUBW","OrderItemCorpusOriginalId":null,"OrderItemCurrency":"HUF","OrderItemExchangeRate":1.00000,"OrderItemOriginalLocation":"HU","OrderItemBuyLocation":"HU","OrderItemPrice":1337.80314,"OrderItemDiscountId":0,"OrderItemDiscountValue":"","DocumentId":25140,"Title":"Romana Gold 10. kötet","PublisherOriginalName":"Harlequin Kiadó","ISBN":"9789634073369"},{"OrderId":77864,"DateAdded":"2016-04-30T15:49:22.61","OrderItemCorpusId":"HUBW","OrderItemCorpusOriginalId":null,"OrderItemCurrency":"HUF","OrderItemExchangeRate":1.00000,"OrderItemOriginalLocation":"HU","OrderItemBuyLocation":"HU","OrderItemPrice":2748.03149,"OrderItemDiscountId":0,"OrderItemDiscountValue":"","DocumentId":25252,"Title":"Az eltűnt lány","PublisherOriginalName":"Harlequin Kiadó","ISBN":"9789634072423"}]
My POJO domain object which should keep the converted data from JSON:
#JsonIgnoreProperties(ignoreUnknown = true)
public class BandWTransaction {
private long OrderId;
private Date DateAdded;
private String OrderItemCurrency;
private double OrderItemExchangeRate;
private String OrderItemBuyLocation;
private double OrderItemPrice;
private String OrderItemDiscountValue;
private long DocumentId;
private String Title;
private String PublisherOriginalName;
private String ISBN;
//getters and setters
Finally the code snippet I use for the rest get request:
String startDate = new SimpleDateFormat("yyyy-MM-dd").format(start.getTime());
String endDate = new SimpleDateFormat("yyyy-MM-dd").format(end.getTime());
UriComponents uri = UriComponentsBuilder.newInstance().scheme("http").host("www.bookandwalk.hu")
.path("/api/AdminTransactionList").queryParam("password", "XXX")
.queryParam("begindate", startDate).queryParam("enddate", endDate).queryParam("corpusid", "HUBW")
.build().encode();
LOG.log(Level.INFO, "{0} were called as a rest call", uri.toString());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.set("User-Agent", "Anything");
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<List<BandWTransaction>> transResponse = restTemplate.exchange(uri.toString(), HttpMethod.GET,
entity, new ParameterizedTypeReference<List<BandWTransaction>>() {
});
List<BandWTransaction> transactions = transResponse.getBody();
When I debug the app I realized that the transactions list includes objects with full of null and 0 values. More precisely, there is no and objcet within the list having other values as 0 and null in the properties.
I have also checked that spring boot automatically registered in the restTemplate.messageConverters ArrayList 9 HttpMessageConverter. The 7th element of this ArrayList is the org.springframework.http.converter.json.MappingJackson2HttpMessageConverter which supports the application/json and application/+json media types.
Any idea is appreciated to solve this problem as I am newbie in spring and in JSON mapping in general.
It seems you have a naming convention issue due to your field variables starts with a uppercase. When Jackson finds a pair getTitle/setTitleasumes that the name of this variable is title (starting with lowercase). Of course, if you change the capitalization of your variables, json properties and java variables has different names, so mapping still fails. The best solution is change your names to meet Java conventions, and use Jackson annotations to define your mappings.
#JsonProperty(value="OrderId")
private int orderId;
#JsonProperty(value="DateAdded")
private Date dateAdded;
Hope it helps.
I can suggest you to write a test and check how fasterxml ObjectMapper read a json and unmarshall json to your object:
ObjectMapper objectMapper = new ObjectMapper();
String somestring = objectMapper.readValue("somestring", String.class);
just replace String with your class and "somestring" with your json. So you check if there is problem with it.
And try to use #JsonPropery cause all this capital letters fields start with looks messy:
#JsonIgnoreProperties(ignoreUnknown = true)
public class BandWTransaction {
#JsonProperty("OrderId")
private long OrderId;
[...]
With this stuff I read json correct. You can come in from other side remove ignoring unknown properties:
#JsonIgnoreProperties(ignoreUnknown = true) //remove it and run test
public class BandWTransaction {
and you get :
(11 known properties: "dateAdded", "orderItemExchangeRate",
"documentId", "orderItemPrice", "orderId", "orderItemBuyLocation",
"orderItemDiscountValue", "orderItemCurrency", "isbn", "title",
"publisherOriginalName"])
So problem in variables naming and you can fix it with #JsonProperty

How to write empty strings for null dates via Jackson + Spring MVC?

I tried using a custom serializer but it seems to never even go there when the value is null, so it never does my code to print empty string:
#JsonSerialize(using = NullDateSerializer.class)
public Timestamp getFinalizedDate() {
return finalizedDate;
}
--
public class NullDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (value == null)
jgen.writeString("");
else
jgen.writeNumber(value.getTime());
}
}
So what do I do then?
I found several threads on writing nulls across the whole app using some ObjectMapper, and though I prefer to do it explicitly like using #JsonSerialize I tried that but it did not even work. I guess the old solutions dont work anymore. Regardless, I want to do it with a #JsonSerialize.
Using jackson 1.9.3 spring 3.2.2
Isn't serialization for converting Object to String? The getter method you wrote seems to be returning a Timestamp (and not a String) ... probably that's why the serializer is not getting invoked.

Set Jackson Timezone for Date deserialization

I'm using Jackson (via Spring MVC Annotations) to deserialize a field into a java.util.Date from JSON. The POST looks like - {"enrollDate":"2011-09-28T00:00:00.000Z"}, but when the Object is created by Spring & Jackson it sets the date as "2011-09-27 20:00:00".
How can I set the proper timezone in Jackson?
Or if that is not the problem, how do I send EST from the JSON message?
Javascript/jQuery:
var personDataView = { enrollDate : new Date($("#enrollDate").val()),
//...other members
};
$.postJSON('/some/path/', personDataView, function(data){
//... handle the response here
});
JSON Message:
{"enrollDate":"2011-09-28T00:00:00.000Z"}
Spring Controller:
#RequestMapping(value="/", method=RequestMethod.POST)
public #ResponseBody String saveProfile(#RequestBody personDataView persondataView, HttpServletRequest request)
{
//...dataView has a java.util.Date enrollDate field
//...other code
}
In Jackson 2+, you can also use the #JsonFormat annotation:
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="America/Phoenix")
private Date date;
If it doesn't work this way then try wrapping Z with single quotes, i.e. pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
Have you tried this in your application.properties?
spring.jackson.time-zone= # Time zone used when formatting dates. For instance `America/Los_Angeles`
If you really want Jackson to return a date with another time zone than UTC (and I myself have several good arguments for that, especially when some clients just don't get the timezone part) then I usually do:
ObjectMapper mapper = new ObjectMapper();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("CET"));
mapper.getSerializationConfig().setDateFormat(dateFormat);
// ... etc
It has no adverse effects on those that understand the timezone-p
I am using Jackson 1.9.7 and I found that doing the following does not solve my serialization/deserialization timezone issue:
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
objectMapper.setDateFormat(dateFormat);
Instead of "2014-02-13T20:09:09.859Z" I get "2014-02-13T08:09:09.859+0000" in the JSON message which is obviously incorrect. I don't have time to step through the Jackson library source code to figure out why this occurs, however I found that if I just specify the Jackson provided ISO8601DateFormat class to the ObjectMapper.setDateFormat method the date is correct.
Except this doesn't put the milliseconds in the format which is what I want so I sub-classed the ISO8601DateFormat class and overrode the format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
method.
/**
* Provides a ISO8601 date format implementation that includes milliseconds
*
*/
public class ISO8601DateFormatWithMillis extends ISO8601DateFormat {
/**
* For serialization
*/
private static final long serialVersionUID = 2672976499021731672L;
#Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
{
String value = ISO8601Utils.format(date, true);
toAppendTo.append(value);
return toAppendTo;
}
}
Looks like older answers were fine for older Jackson versions, but since objectMapper has method setTimeZone(tz), setting time zone on a dateFormat is totally ignored.
How to properly setup timeZone to the ObjectMapper in Jackson version 2.11.0:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Warsaw"));
Full example
#Test
void test() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Warsaw"));
ZonedDateTime now = ZonedDateTime.now();
String converted = objectMapper.writeValueAsString(now);
ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
System.out.println("serialized: " + now);
System.out.println("converted: " + converted);
System.out.println("restored: " + restored);
Assertions.assertThat(now).isEqualTo(restored);
}
`
Just came into this issue and finally realised that LocalDateTime doesn't have any timezone information. If you received a date string with timezone information, you need to use this as the type:
ZonedDateTime
Check this link
Your date object is probably ok, since you sent your date encoded in ISO format with GMT timezone and you are in EST when you print your date.
Note that Date objects perform timezone translation at the moment they are printed. You can check if your date object is correct with:
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTime(date);
System.out.println (cal);
I had same problem with Calendar deserialization, solved extending CalendarDeserializer.
It forces UTC Timezone
I paste the code if someone need it:
#JacksonStdImpl
public class UtcCalendarDeserializer extends CalendarDeserializer {
TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
#Override
public Calendar deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT) {
Calendar cal = Calendar.getInstance(TZ_UTC);
cal.clear();
cal.setTimeInMillis(jp.getLongValue());
return cal;
}
return super.deserialize(jp, ctxt);
}
}
in JSON model class just annotate the field with:
#JsonDeserialize(using = UtcCalendarDeserializer.class)
private Calendar myCalendar;
For anyone struggling with this problem in the now (Feb 2020), the following Medium post was crucial to overcoming it for us.
https://medium.com/#ttulka/spring-http-message-converters-customizing-770814eb2b55
In our case, the app uses #EnableWebMvc and would break if removed so, the section on 'The Life without Spring Boot' was critical. Here's what ended up solving this for us. It allows us to still consume and produce JSON and XML as well as format our datetime during serialization to suit the app's needs.
#Configuration
#ComponentScan("com.company.branch")
#EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2XmlHttpMessageConverter(
new Jackson2ObjectMapperBuilder()
.defaultUseWrapper(false)
.createXmlMapper(true)
.simpleDateFormat("yyyy-mm-dd'T'HH:mm:ss'Z'")
.build()
));
converters.add(1, new MappingJackson2HttpMessageConverter(
new Jackson2ObjectMapperBuilder()
.build()
));
}
}

How to serialize in jackson json null string to empty string

I need jackson json (1.8) to serialize a java NULL string to an empty string. How do you do it?
Any help or suggestion is greatly appreciated.
Thanks
See the docs on Custom Serializers; there's an example of exactly this, works for me.
In case the docs move let me paste the relevant answer:
Converting null values to something else
(like empty Strings)
If you want to output some other JSON value instead of null (mainly
because some other processing tools prefer other constant values --
often empty String), things are bit trickier as nominal type may be
anything; and while you could register serializer for Object.class, it
would not be used unless there wasn't more specific serializer to use.
But there is specific concept of "null serializer" that you can use as
follows:
// Configuration of ObjectMapper:
{
// First: need a custom serializer provider
StdSerializerProvider sp = new StdSerializerProvider();
sp.setNullValueSerializer(new NullSerializer());
// And then configure mapper to use it
ObjectMapper m = new ObjectMapper();
m.setSerializerProvider(sp);
}
// serialization as done using regular ObjectMapper.writeValue()
// and NullSerializer can be something as simple as:
public class NullSerializer extends JsonSerializer<Object>
{
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider)
throws IOException, JsonProcessingException
{
// any JSON value you want...
jgen.writeString("");
}
}