Passing arguments to Java class used in #JsonSerialize and #JsonDeserialize - java

I have a scenario which I am not sure how to google, so I'd open a question myself. If its a duplicate please link it here so I can give that question credit.
Anyway,
I have a Java Object with fields that I have the following annotations:
#JsonSerialize(using = CustomSerializer.class)
#JSonDeserialize(using = CustomDeserializer.class)
private Date systemDate;
The business dictates that certain systemDate values in different database have different time zones (not sure why they did not standardize to UTC).
Here is an example of my CustomSerializer.java:
#Override
public void serialize(Date, value, JsonGenerator gen, SerializerProvider arg2) throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yy");
formatter.setTimeZone(TimeZone.getTimeZone("CET"));
if (value == null) {
gen.writeNull();
} else {
gen.writeString(formatter.formate(value.getTime()));
}
}
Instead of creating a new serializer class per timezone, is there a way to pass the timezone argument(s) to this class (and also my Deserializer class)?

You need to create a custom annotation as below:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface DateSerialize {
String timeZone();
}
Now in the field systemDate add this new annotation by passing the timezone
#DateSerialize(timeZone = "UTC")
#JsonSerialize(using = CustomSerializer.class)
#JSonDeserialize(using = CustomDeserializer.class)
private Date systemDate;
Your serializer class should implement ContextualSerializer this would allow us to get the BeanProperty from where we could get the annotation details.
public class DateSerializer extends JsonSerializer<Date> implements ContextualSerializer {
String timeZone;
public DateSerializer() {
this("UTC");
}
public DateSerializer(String tz) {
timeZone = tz;
}
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yy");
formatter.setTimeZone(TimeZone.getTimeZone(timeZone));
if (value == null) {
gen.writeNull();
} else {
gen.writeString(formatter.format(value.getTime()));
}
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
DateSerialize annotation = property.getAnnotation(DateSerialize.class);
String tz = (annotation == null) ? "UTC" : annotation.timeZone();
return new DateSerializer(tz);
}
}
Similarly you could create a deserializer by implementing ContextualDeserializer.

Related

How to create a customized Jackson annotation to append property on serialization

I would like to create a customized Jackson annotation like that:
#JacksonAnnotationsInside
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.TYPE})
//... Other Jackson annotations...
#interface SelfLink {
Class<? extends Entity> type();
String uriTemplate() default "";
}
I would like to use this annotation on classes or fields like this:
#Getter
#SelfLink(type = Alpha.class)
class Alpha extends Entity {
private String name;
public Alpha(Long id, String name) {
super(id);
this.name = name;
}
}
#Getter
class Beta {
private String uuid;
#SelfLink(type = Gamma.class)
private Entity data;
}
#Getter
class Gamma extends Entity {
private String stuff;
public Gamma(Long id, String stuff) {
super(id);
this.stuff = stuff;
}
}
The customized Jackson annotation would be used to append extra fields. I tried to use #JsonSerialize(using = SelfLinkSerializer.class) on the SelfLink annotation to define a Serializer:
#JsonComponent
class SelfLinkSerializer extends StdSerializer<Object> {
private Gson gson = new Gson();
private Mirror mirror = new Mirror();
#Autowired
private LinkResolver linkResolver;
#SuppressWarnings("unused")
private SelfLinkSerializer() {
this(Object.class,
new LinkResolver() {
#Override
public <T extends Entity> String resolve(Class<? extends Entity> type, T instance) {
return "always-null";
}
}
);
}
SelfLinkSerializer(Class<Object> t, LinkResolver linkResolver) {
super(t);
this.linkResolver = linkResolver;
}
#Autowired
public SelfLinkSerializer(LinkResolver linkResolver) {
this(Object.class, linkResolver);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
this.serializeContentWithoutThisSerializer(value, gen, provider);
gen.writeEndObject();
}
private void serializeContentWithoutThisSerializer(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
String jsonContent = this.serializeContentViaGson(value, gen, provider);
gen.writeRaw(jsonContent);
}
private String serializeContentViaGson(Object value, JsonGenerator gen, SerializerProvider provider) {
JsonElement jsonElement = this.gson.toJsonTree(value);
SelfLink selfLink = this.mirror.on(value.getClass()).reflect().annotation(SelfLink.class).atClass();
if(selfLink != null) {
Class<? extends Entity> type = selfLink.type();
String link = value instanceof Entity ? this.linkResolver.resolve(type, (Entity) value) : null;
jsonElement.getAsJsonObject().addProperty("link", link);
}
String json = this.gson.toJson(jsonElement);
String trimmed = CharMatcher.is('{').trimFrom(json);
trimmed = CharMatcher.is('}').trimTrailingFrom(trimmed);
return trimmed;
}
}
The original idea was to start the JSON ("{"), use Jackson engine to generate the content, my serializer would resolve the link, append it on the JSON ("link":"..."), and close the JSON ("}"). But I hit a wall: I did not find a way reuse Jackson. My workaround so far is to use Gson, but that is a monstrosity. Gson do not honor Jackson annotations. I would need to create an algorithm to convert all Jackson annotations and that is a "NO-NO".
I am kinda locked on some versions of Spring Boot and Jackson (1.5.8.RELEASE and 2.8.10 respectively). I created a Gist with the whole example, and a pom.xml.
I have a secondary issue because even as a #JsonComponent, Spring is not injecting the #Autowired LinkResolver linkResolver. For now I also do not know how to make #SelfLink work on fields like on Beta class. Bur for now my question is:
How is it possible to use the Jackson API to generate the string with the JSON representation for the value param in the public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException method?

Is there a common code to change the date format(timestamp) to Number format

I am using SpringBoot 2.2. date format is "validFrom": "2013-12-31T18:30:00.000+0000"
But I want in number format (like 1411471800000).
In my entity I included the below code snippet which worked in Number format.
#JsonProperty("updDate")
**#JsonFormat(shape = JsonFormat.Shape.NUMBER)**
private Date updDate;
To achieve that, I will have to do in all my entities.Is there a way where I can make one change and it will apply for all date formats.
Please advise
You can use custom Serializer for Date type which will used to serialize Date type.
public class DateSerializer extends StdSerializer<Date> {
private static final long serialVersionUID = -7880057299936791237L;
public JacksonLocalDateSerializer() {
this(null);
}
public JacksonLocalDateSerializer(Class<Date> type) {
super(type);
}
#Override
public void serialize(Date value, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeNumber(value.getTime());
}
}
and add it in object mapper so that Date type object always serialize using your custom serializer
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper configureObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Date.class, new DateSerializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
}

Spring REST timezone issue when using XMLGregorianCalendar

I have a spring rest service that accepts and gives json output.
#PostMapping(path = "/path", consumes = {"application/json"}, produces = {"application/json"})
public ResponseEntity<RequestData> method(#RequestBody RequestData request){
return request;
}
RequestData contains several dates (XMLGregorianCalendar). I cannot change the type, since it is generated from xsd. To get dates with the original time zones, I used the parameter
spring.jackson.date-format: yyyy-MM-dd'T'HH:mm:ssZ
Request
{
"date1":"2020-02-28T09:26:59+09:00",
"date2":"2020-01-10T12:46:29+04:00",
"date3":"2020-03-15T11:32:43+08:00"
}
From this request, I got an XMLGregorianCalendar with different time zones.
But when sending a response message, the dates are converted to 0 time zone.
Response
{
"date1":"2020-02-28T00:26:59+0000",
"date2":"2020-01-10T08:46:29+0000",
"date3":"2020-03-15T03:32:43+0000"
}
What settings need to be done on jackson to get non-zero time zones in the response? It is necessary that the response time zones returned in the request.
Or maybe jackson does not know how to do this and always converts the date to a single time zone? In that case, which library to use?
Thanks!
Solution
You must create a serializer and deserializer. Then you need to override the existing ObjectMapper.
If only the serializer is overrided, then upon receipt of the data, the time zone will be normalized (reduced to +00:00), therefore it is also necessary to override the deserializer.
Serializer:
public class XMLGCSerializer extends JsonSerializer<XMLGregorianCalendar> {
#Override
public void serialize(XMLGregorianCalendar value,
JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
gen.writeObject(value.toString());
}
}
Deserializer:
public class XMLGCDeserializer extends JsonDeserializer<XMLGregorianCalendar> {
#Override
public XMLGregorianCalendar deserialize(JsonParser parser, DeserializationContext context) throws IOException {
String stringDate = parser.getText();
try {
return DatatypeFactory.newInstance().newXMLGregorianCalendar(stringDate);
} catch (Exception e) {
throw new RuntimeException(e);
//or return null
}
}
}
Override ObjectMapper
#Component
public class JacksonConfig {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(XMLGregorianCalendar.class, new XMLGCSerializer());
s.addDeserializer(XMLGregorianCalendar.class, new XMLGCDeserializer());
objectMapper.registerModule(s);
}
#Bean
public ObjectMapper getContext() {
return objectMapper;
}
}
You can create a seperate class to handle serialization by yourself. Here is an example:
class XMLGCSerializer extends JsonSerializer<XMLGregorianCalendar> {
#Override
public void serialize(XMLGregorianCalendar value,
JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
gen.writeObject(value.toString());
}
}
Now you just need to annotate your fields in RequestData:
class RequestData{
#JsonSerialize(using = XMLGCSerializer.class)
XMLGregorianCalendar date1;
//...
}

Date from Spring to web as timestamp using Jakson

I have Match class and field Date start. My goal is get start as timestamp. I use Spring, AngularJs, and jackson as json converter.
Spring Controller:
#RequestMapping(value = "/web2/getMatch", method =RequestMethod.POST)
public #ResponseBody Match getPicksHistory() {
PickDAO pd = new PickDAO();
return pd.getMatch();
}
On AgularJS controler:
var res = $http.post(urlSer.url+"web2/getMatch");
res.success(function(data, status, headers, config) {
// now returns data.start = "Aug 8, 2015 7:00:00 PM"
// My goal is get as timestamp
});
I assume that by 'timestamp' you mean a numeric timestamp as opposed to a textual representation. You can use a custom ObjectMapper:
#Component
#Primary
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
}
}
I use jackson-databind:2.6.1 and JsonSerializer
#Component
public class JsonDateSerializer extends JsonSerializer<Date>{
#Override
public void serialize(Date date, JsonGenerator gen,
SerializerProvider serializers) throws IOException,
JsonProcessingException {
gen.writeNumber(date.getTime());
}
}

JSON serialization strategy for dates

The problem I am having is that I have some consumers that are Java and some that are browsers. My target browsers are IE7+ (json3 for IE7 only) & Chrome.
For a browser I wish to have the date deserialize to a Date JavaScript object (using the JSON.parse() method. For a Java consumer I wish to deserialize to a java.util.Date Java object.
Given that I can't change anything on the browser side. I have to do serialize the messages to something like this:
{ myDate: new Date(<EPOCH HERE>) }
Which of course will cause a problem for Java deserializer. However, I am hoping there is something I can do with Gson to make this work...amy ideas?
Or should I take a different strategy altogether?
I usually use the annotation #JsonSerialize and #JsonDeserialize to deal with this problem. I also use ISO8601 format as a standard for our REST API dates.
#JsonSerialize(using=JsonDateSerializer.class)
#JsonDeserialize(using=JsonDateDeserializer.class)
private Date expiryDate;
JsonDateSerializer class
#Component
public class JsonDateSerializer extends JsonSerializer<Date>
{
// ISO 8601
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException
{
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
JsonDateDeserializer class
#Component
public class JsonDateDeserializer extends JsonDeserializer<Date>
{
// ISO 8601
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException
{
try
{
return dateFormat.parse(jsonParser.getText());
}
catch (ParseException e)
{
throw new JsonParseException("Could not parse date", jsonParser.getCurrentLocation(), e);
}
}
}

Categories

Resources