How to stop jackson timezone conversion and not apply local offset - java

In my java dto i am converting json date ("dispOn": "11/28/2020",) to java.util.Date.since i am not sending any timezone jackson is taking UTC as default time zone but when desterilizing it is taking the offset of CST(My local time)as a result, date in my code(11/27/2020) is one day behind
import java.util.Date;
#JsonFormat(pattern = "MM/dd/yyyy")
private Date dispOn;
How can i get the same date sending in json into my code

As Andreas mentioned in his comment, it is sufficient to change from Date to LocalDate.
For additional info, I used LocalDateTime to show you how the format should be configured. I'm also using Lombok's annotations on class-level for logging and getter/setter generation.
Keep in mind that LocalDate and LocalDateTime are only available since Java 8.
Below a complete example of a DTO in which I parse a JSON
#Slf4j
#Getter
#Setter
#JsonIgnoreProperties(ignoreUnknown = true) // optional annotation
public class MyMessage implements Deserializer<MyMessage> {
#JsonProperty("EVENT_TIMESTAMP")
#JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime eventTimestamp;
#JsonProperty("MESSAGE")
private String message;
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
Deserializer.super.configure(configs, isKey);
}
#Override
public MyMessage deserialize(String s, byte[] bytes) {
ObjectMapper mapper = new ObjectMapper();
MyMessage event = null;
try {
event = mapper
.registerModule(new JavaTimeModule())
.readValue(bytes, MyMessage.class);
} catch (Exception e) {
log.error("Something went wrong during the deserialization of the MyMessage: {}", e.getMessage());
}
return event;
}
#Override
public MyMessage deserialize(String message, Headers headers, byte[] data) {
return Deserializer.super.deserialize(message, headers, data);
}
#Override
public void close() {
Deserializer.super.close();
}
And the JSON I'm sending:
{
"EVENT_TIMESTAMP": "2022-03-15T14:14:05.945",
"MESSAGE": "Test 123"
}

Related

How to specify DateTime in GraphQL schema?

I am building my GraphQL schema for my project and one of my models has a DateTime format.
How do I write out date formats on my GraphQL schema?
I tried DateTime or Date but nothing shows up.
This is the model:
public Integer Id;
public String name;
public String description;
public LocalDate birthDate;
This is what's in my GraphQL schema:
type Pet {
id: ID!
name: String!
description: String
birthDate: DateTime
}
But it says:
Unknown type DateTime
Create a custom scalar for your types that is not recognized by your framework.
I am not sure which graphql-java based framework you are using. I assume you are using the official Spring for GraphQL from Spring team.
Create a custom scalar, eg my LocalDateTime scalar.
public class LocalDateTimeScalar implements Coercing<LocalDateTime, String> {
#Override
public String serialize(Object dataFetcherResult) throws CoercingSerializeException {
if (dataFetcherResult instanceof LocalDateTime) {
return ((LocalDateTime) dataFetcherResult).format(DateTimeFormatter.ISO_DATE_TIME);
} else {
throw new CoercingSerializeException("Not a valid DateTime");
}
}
#Override
public LocalDateTime parseValue(Object input) throws CoercingParseValueException {
return LocalDateTime.parse(input.toString(), DateTimeFormatter.ISO_DATE_TIME);
}
#Override
public LocalDateTime parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof StringValue) {
return LocalDateTime.parse(((StringValue) input).getValue(), DateTimeFormatter.ISO_DATE_TIME);
}
throw new CoercingParseLiteralException("Value is not a valid ISO date time");
}
}
Register it in your custom RuntimeWiring bean, check here.
public class Scalars {
public static GraphQLScalarType localDateTimeType() {
return GraphQLScalarType.newScalar()
.name("LocalDateTime")
.description("LocalDateTime type")
.coercing(new LocalDateTimeScalar())
.build();
}
}
#Component
#RequiredArgsConstructor
public class PostsRuntimeWiring implements RuntimeWiringConfigurer {
private final DataFetchers dataFetchers;
#Override
public void configure(RuntimeWiring.Builder builder) {
builder
//...
.scalar(Scalars.localDateTimeType())
//...
.build();
}
}
If you are using Scalars in other graphql-java based frameworks(GraphQL Java, GraphQL Java Kickstart, GraphQL Kotlin, GraphQL SPQR, Netflix DGS etc) and spring integrations, check my Spring GraphQL Sample. The back-end principle is similar, just some different config.

Formatter<LocalDateTime> not getting registered with Spring Boot container in REST application

I have implemented a Formatter in a REST Spring Boot web service application to format all LocalDateTime (Java 8) attributes so that they get displayed in a specific format in the web service response.
public class LocalDateTimeFormatter implements Formatter<LocalDateTime> {
#Override
public String print(LocalDateTime temporal, Locale locale) {
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss");
return formatter.format(temporal);
}
#Override
public LocalDateTime parse(String text, Locale locale)
throws ParseException {
return LocalDateTime.parse(text);
}
}
The formatter has also been registered as shown below so that it works at the application level in order to avoid annotating all LocalDateTime attributes individually
#Configuration
public class ContentFormatter implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateTimeFormatter());
}
}
But no formatting is applied to the response from the web service, accessDate being the LocalDateTime type of attribute
{
"content" : "Hello, World!",
"id" : 0,
"accessDate" : "2020-04-07T19:56:41.48"
}
You probably don't even need to implement your formatted and register it. Just annotate your LocalDateTime property as follows:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
public LocalDateTime getTime() {
return time;
}
Also, you might need to add the following dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.0</version>
</dependency>
See this question and its answer for details:
Spring Data JPA - ZonedDateTime format for json serialization
You can use StdSerializer
public class JacksonLocalDateSerializer extends StdSerializer<LocalDate> {
private static final long serialVersionUID = -7880057299936771237L;
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withResolverStyle(ResolverStyle.STRICT);
public JacksonLocalDateSerializer() {
this(null);
}
public JacksonLocalDateSerializer(Class<LocalDate> type) {
super(type);
}
#Override
public void serialize(LocalDate value, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(formatter.format(value));
}
}
Then add configuration for the serializer for applicable the entire application
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper configureObjectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new JacksonLocalDateTimeSerializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(javaTimeModule);
return mapper;
}
}

Java ModelMapper map an object in an object

I have the following DTO and VO:
VO
public class ProjectVO {
private Date fechaInicio;
private Date fechaFin;
}
DTO
public class ProjectDTO {
private String fechaInicio;
private String fechaFin;
}
And the following converter to convert the strings to dates:
Converter<String, Date> dateConverter = new Converter<String, Date>()
{
public Date convert(MappingContext<String, Date> context)
{
Date date;
try {
date = new SimpleDateFormat("dd/MM/yyyy").parse(context.getSource());
} catch (ParseException e) {
throw new DateFormatException();
}
return date;
}
};
modelMapper.addConverter(dateConverter);
If I convert a single String to a date using modelmapper it'll work perfectly with this converter.
But now I need to convert my ProjectDTO object to a ProjectVO one and I am getting an error saying that it cannot convert a String to a Date. I suspect this is because the Date is inside the Project object. Am I right? How can I solve this?
Thanks.
Okay it works like a charm and automatically detects it if I use this:
modelMapper.createTypeMap(String.class, Date.class);
and then add the custom converter I created :)

Spring Boot doesn't use custom deserializer for ZonedDateTime

I am trying to setup my spring boot configuration to use my custom serializer and deserializer.
Deserializer code
public class CustomZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
#Override
public ZonedDateTime deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
TextNode node = (TextNode) oc.readTree(jsonParser);
String dateString = node.textValue();
return ZonedDateTime.parse(dateString, CustomZonedDateTimeSerializer.formatter);
}
#Override
public Class<?> handledType() {
return ZonedDateTime.class;
}
}
Serializer code
public class CustomZonedDateTimeSerializer extends JsonSerializer<ZonedDateTime> {
#Override
public Class<ZonedDateTime> handledType() {
return ZonedDateTime.class;
}
public final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
#Override
public void serialize(ZonedDateTime date, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
final String dateString = date.format(formatter);
generator.writeString(dateString);
}
}
This my configuration:
#Configuration
public class JacksonConfiguration {
#Autowired
Jackson2ObjectMapperBuilder jacksonBuilder;
#PostConstruct
public void configureJackson() {
final CustomZonedDateTimeSerializer serializer = new CustomZonedDateTimeSerializer();
jacksonBuilder.serializers(serializer);
final CustomZonedDateTimeDeserializer deserializer = new CustomZonedDateTimeDeserializer();
jacksonBuilder.deserializers(deserializer);
}
}
I am creating a REST API so I have this in my controller:
#RequestMapping(value = "date", method = { RequestMethod.GET })
#ApiOperation(value = "", notes = "")
public ResponseWrapper<String> testDATE(
#RequestParam #ApiParam(value = "", required = true) ZonedDateTime date) {
System.out.println(date.toString());
return new ResponseWrapper<String>(date.toString());
}
So far I have tried:
creating a custom module and adding it as a #Bean in the configuration, adding the serializer and deserializer to the jackonBuilder object in configuration and something like the code in this example
Am I missing something or doing something wrong?
Each time I try I get the following error:
"Failed to convert value of type 'java.lang.String' to required type
'java.time.ZonedDateTime'; nested exception is
org.springframework.core.convert.ConversionFailedException: Failed to
convert from type [java.lang.String] to type
[#org.springframework.web.bind.annotation.RequestParam
#io.swagger.annotations.ApiParam java.time.ZonedDateTime] for value
'2018-07-10T00:00:00+0000'; nested exception is
java.lang.IllegalArgumentException: Parse attempt failed for value
[2018-07-10T00:00:00+0000]"
I am sending the following value each time: 2018-07-10T00:00:00+0000 and the value doesn't even reach the deserializer where I have my breakpoint.
Add this bean in your configuration class:
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
final CustomZonedDateTimeSerializer serializer = new CustomZonedDateTimeSerializer();
builder.serializers(serializer);
final CustomZonedDateTimeDeserializer deserializer = new CustomZonedDateTimeDeserializer();
builder.deserializers(deserializer);
return builder;
}
Seems like I could not send ZonedDateTime in GET requests. Once I changed to POST, Spring was able to parse the date automatically.
I only added spring.jackson.deserialization.adjust_dates_to_context_time_zone=true so it takes the timezone into consideration.
If someone will want to send it in GET he will have to send a string and parse that instead of defining ZonedDateTime as a parameter.

Jackson JsonDeserialize not being called for #QueryParam

I have mapped a custom deserializer to convert Strings on dd/MM/yyyy pattern to LocalDate so I can call my services with a more readable signature..
This is my dto class that is used as a Jersey #BeanParam to transport data between layers:
public class ProdutoFilterDto implements FilterDto {
private static final long serialVersionUID = -4998167328470565406L;
#QueryParam("dataInicial")
#JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataInicial;
#QueryParam("dataInicial")
#JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataFinal;
public LocalDate getDataInicial() {
return dataInicial;
}
public void setDataInicial(LocalDate dataInicial) {
this.dataInicial = dataInicial;
}
public LocalDate getDataFinal() {
return dataFinal;
}
public void setDataFinal(LocalDate dataFinal) {
this.dataFinal = dataFinal;
}
}
and this is my custom deserializer:
public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
final String data = p.getValueAsString();
return (LocalDate) formatter.parse(data);
}
}
Its being used on this jersey service:
#Path("produto")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ProdutoService {
...
#GET
#Path("query")
#Override
public Response query(
#QueryParam("offset") #DefaultValue(value = "0") Integer offSet,
#QueryParam("limit") #DefaultValue(value = "10") Integer limit,
#BeanParam ProdutoFilterDto filter) { ... }
...
}
I am calling like this:
${host goes here}/produto/query?dataInicial=11/09/1992
The problem is that the deserializer method is never called and the bean param variable remains null..
MessageBodyReaders aren't used for #QueryParam. You seem to be expecting the Jackson MessageBodyReader to handle this deserialization, but it doesn't work like that.
Instead you will want to use a ParamConverter, which will need to be registered through a ParamConverterProvider. For example:
#Provider
public class LocalDateParamConverterProvider implements ParamConverterProvider {
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
#Override
public <T> ParamConverter<T> getConverter(
Class<T> rawType, Type genericType, Annotation[] antns) {
if (LocalDate.class == rawType) {
return new ParamConverter<T>() {
#Override
public T fromString(String string) {
try {
LocalDate localDate = LocalDate.parse(string, formatter);
return rawType.cast(localDate);
} catch (Exception ex) {
throw new BadRequestException(ex);
}
}
#Override
public String toString(T t) {
LocalDate localDate = (LocalDate) t;
return formatter.format(localDate);
}
};
}
return null;
}
}
Now LocalDate will work with #QueryParam and other #XxxParams also.
Some things to note:
If your goal is to parse both your #XxxParams and your JSON body into a bean this will not work. I'm not sure how that would work, but I'm sure it would involve a lot of hacking, and I wouldn't recommend it.
Your cast to (LocalDate) won't work. It's an illegal cast to java.time.format.Parsed. See correct way in code example.
Related to the above point. I was pulling out my hair for a good hour trying to figure out why I was getting a 404, using your parse code. With a 404, the last place I thought to look was in the ParamConverter. But it seems any uncaught exceptions that are thrown in the ParamConverter, will cause a 404. Doesn't make much sense right? The head pounding led me to this, which led me to this, which seems to be a poor specification
"if the field or property is annotated with
#MatrixParam, #QueryParam or #PathParam then an implementation MUST generate an instance of
NotFoundException (404 status) that wraps the thrown exception and no entity
"
Moral of the story: make sure to catch any possible exceptions in the ParamConverter!
See Also:
Good article on ParamConverters

Categories