Hi I try to create a controller which will accept request-parameter as LocalDateTime.
ex: /api/actions?page=0&size=10&from=2018-05-02T20:20:20&to=2018-06-02T20:20:20
at controller if I using code bellow it work:
#RequestParam(value = "from")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime from,
#RequestParam(value = "to")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime to
But I want to move #DateTimeFormat to globally configuration, and I choose ObjectMapper:
I create a bean in configuration:
#Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(
LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
And try
#Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(
LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
This is dateTimeFormat value: yyyy-MM-dd'T'HH:mm:ss.SS
Both of 2 ways above are not working, It said:
class org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#org.springframework.web.bind.annotation.RequestParam java.time.LocalDateTime] for value '2018-05-02T20:20:20'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2018-05-02T20:20:20]
My jackson version:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.8.8</version>
</dependency>
Am I missing something?
Thank you for your time.
class org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException
I think here you need to use JsonSerializer and JsonDeserializer.
So, when request comes you use JsonDeserializer, it will convert your date of String format to required Date format. Here is a code,
#Component
public class DateDeSerializer extends JsonDeserializer<Date> {
public final SimpleDateFormat formatter = new SimpleDateFormat("date format");
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
if (jp.getCurrentToken().equals(JsonToken.VALUE_STRING)) {
try {
return formatter.parse(jp.getText());
} catch (ParseException e) {
// throw exception
}
}
return null;
}
#Override
public Class<Date> handledType() {
return Date.class;
}
}
To format your response use JsonSerializer. Here is a sample code,
#Component
public class DateSerializer extends JsonSerializer<Date> {
DateFormat formatter = new SimpleDateFormat("date format");
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeObject(formatter.format(value));
}
#Override
public Class<Date> handledType() {
return Date.class;
}
}
Problem is I pass LocalDateTime at requestParam, but I using ObjectMapper which only work with request's body.
And to resolve my issue I create new component LocalDateTimeConverter and remove bean of ObjectMapper.
#Component
public class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
private final DateTimeFormatter formatter;
#Autowired
public LocalDateTimeConverter(#Value("${dateTime.format}") String dateTimeFormat) {
this.formatter = DateTimeFormatter.ofPattern(dateTimeFormat);
}
#Override
public LocalDateTime convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
return LocalDateTime.parse(source, formatter);
}
}
Related
after adding a custom serializer for Instant toString is failing on JsonNode.
I have the below configuration for ObjectMapper.
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.registerModule(new SimpleModule());
MAPPER.registerModule(new ParameterNamesModule());
MAPPER.registerModule(new Jdk8Module());
MAPPER.registerModule(new JavaTimeModule());
SimpleModule module = new SimpleModule();
module.addSerializer(Instant.class, new InstantSerializer());
module.addDeserializer(Instant.class,new InstantDeSerializer());
MAPPER.registerModule(module);
MAPPER.setVisibility(MAPPER.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(ANY)
.withGetterVisibility(NONE)
.withSetterVisibility(NONE)
.withCreatorVisibility(ANY));
MAPPER.disable(FAIL_ON_EMPTY_BEANS);
MAPPER.disable(FAIL_ON_UNKNOWN_PROPERTIES);
MAPPER.disable(WRITE_DATES_AS_TIMESTAMPS);
MAPPER.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
}
Below are Serializer and De-serializer for Instant.
public class InstantSerializer extends StdSerializer<Instant> {
public InstantSerializer() {
this(null);
}
public InstantSerializer(Class<Instant> t) {
super(t);
}
#Override
public void serialize(
Instant value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeEmbeddedObject(value);
}
}
public class InstantDeSerializer extends StdDeserializer<Instant> {
public InstantDeSerializer() {
this(null);
}
protected InstantDeSerializer(final Class<?> vc) {
super(vc);
}
#Override
public Instant deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JacksonException {
if (p.currentToken() == JsonToken.VALUE_EMBEDDED_OBJECT) {
return (Instant) p.getEmbeddedObject();
}
else if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) {
return Instant.ofEpochMilli(p.getLongValue());
}
else if (p.currentToken() == JsonToken.VALUE_STRING) {
return Instant.parse(p.getValueAsString());
}
throw new RuntimeException();
}
}
I have below class
public class A{
private Instant time;
public Instant getTime() {
return time;
}
public void setTime(final Instant time) {
this.time = time;
}
}
below is the test
#Test
public void test() {
A a = new A();
a.setTime(Instant.now());
final JsonNode jsonNode = MAPPER.readTree(a);
System.out.println(jsonNode);
}
I get the below exception though JavaTimeModule is registered. could someone help me Thanks in advance
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276)
at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:1118)
at com.fasterxml.jackson.databind.node.POJONode.serialize(POJONode.java:115)
at com.fasterxml.jackson.databind.node.ObjectNode.serialize(ObjectNode.java:328)
at com.fasterxml.jackson.databind.ser.std.SerializableSerializer.serialize(SerializableSerializer.java:39)
at com.fasterxml.jackson.databind.ser.std.SerializableSerializer.serialize(SerializableSerializer.java:20)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1514)
at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1215)
at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1085)
at com.fasterxml.jackson.databind.node.InternalNodeMapper.nodeToString(InternalNodeMapper.java:30)
... 92 more
I have a JSON:
{
"stringField" : 1234,
"booleanField": true,
"numberField": 1200.00
}
I use object mapper to deserialize the json into:-
#Data
class SomeClass {
String stringField;
boolean booleanField;
float numberField;
}
I would like the objectMapper to throw an error because, the values for String fields must be double quoted according to the json spec. How can i get objectMapper to throw an error?
You can write custom string deserializer.(i assume you are using spring)
#Configuration
public class JacksonConfiguration {
#Bean
SimpleModule jacksonDeserializerConfiguration() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
if (!parser.hasToken(JsonToken.VALUE_STRING)) {
//throw ex what do u want
throw new RuntimeException("String not include quote");
}
return StringDeserializer.instance.deserialize(parser, context);
}
});
return module;
}
}
This should fix your issue.
class SomeClass {
#JsonDeserialize(using=ForceStringDeserializer.class)
public String stringField;
public boolean booleanField;
public float numberField;
}
class ForceStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
throw deserializationContext.wrongTokenException(jsonParser, JsonToken.VALUE_STRING, "Attempted to parse Integer to String but this is forbidden");
}
return jsonParser.getValueAsString();
}
}
You just need to setup jackson objectmapper like this
JsonFactory factory = new JsonFactory();
factory.disable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
ObjectMapper mapper = new ObjectMapper(factory)
This should throw error during serialization/deserilaization
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;
}
}
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.
I'm on a project that require FasterXML and ZonedDateTime. It is mandatory to not use annotation like using=Deserializer.class because we want to keep the same format of date on all the project.
That being said, I come here to ask help because I can't figure out why my Deserializer is instanciate but its method deserialize() is not call ...
Here my configuration :
The serializer :
public class ZonedDateTimeSerializer extends JsonSerializer<ZonedDateTime> {
/**
* The date time formatter to use
*/
DateTimeFormatter dtf;
/**
* Instanciate a new serializer
*
* #param dtf The datetime formatter
*/
public ZonedDateTimeSerializer(DateTimeFormatter dtf) {
System.out.println("ZonedDateTimeSerializer()");
this.dtf = dtf;
}
#Override
public void serialize(ZonedDateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
System.out.println("ZonedDateTimeSerializer::serialize()");
if (null != dateTime) {
jsonGenerator.writeString(dateTime.format(dtf));
}
}
}
The deserializer :
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
/**
* The date time formatter to use
*/
DateTimeFormatter dtf;
/**
* Instanciate a new deserializer
*
* #param dtf The datetime formatter
*/
public ZonedDateTimeDeserializer(DateTimeFormatter dtf) {
Preconditions.checkNotNull(dtf, "Date time formatter is null");
System.out.println("ZonedDateTimeDeserializer()");
this.dtf = dtf;
}
#Override
public ZonedDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
System.out.println("ZonedDateTimeDeserializer::deserialize()");
return ZonedDateTime.parse(jsonParser.getText(), dtf);
}
}
This two add to the mapper in a mapper provider :
#Provider
public class JacksonConfigurator implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper = new ObjectMapper();
/**
* Constructor
*/
public JacksonConfigurator() {
System.out.println("new ObjectMapperResolver()");
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
SimpleModule xxxModule = new SimpleModule("XXXX", new Version(2, 0, 0, null))
.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(sdf))
.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer(sdf));
mapper.registerModule(xxxModule);
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return mapper;
}
}
And register here in a glassfish jersey ResourceConfig;
#ApplicationPath("api")
public class ApplicationRessourceConfig extends ResourceConfig {
/**
* Define application config
*/
public ApplicationRessourceConfig() {
// File to parse
packages(true, "com.xxx.xxx");
// CDI to Jersey
register(new WebServiceBinder());
// Jackson config
register(new JacksonConfigurator());
// Role
register(RolesAllowedDynamicFeature.class);
}
}
I can't test it in production or development until the tests are ok. So I make the test like :
public class xxxTest extends JerseyTest {
...
#Override
protected Application configure() {
...
ResourceConfig config = new ResourceConfig(...);
config.register(new JacksonConfigurator());
return config;
}
#Test
public void xxx() {
...
response.bufferEntity();
response.readEntity(EntityWithZonedDateTime.class);
...
}
}
When I was using the annotation #JsonDeserializer on each ZonedDateTime property it worked perfectly but now, the Serializer constructor is logged, serialize() is logged too, event the Deserializer constructor is logged, but not the deserialize() method.
Instead I have :
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.ZonedDateTime] from String value ('2012-06-30T12:30:40.000+0000'); no single-String constructor/factory method
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#3af4e0bf; line: 1, column: 46] (through reference chain: com.xxx.xxx.resources.utils.Xxxx["beginDate"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:843)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:277)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:284)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1150)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:153)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:144)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:523)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:101)
at com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap.findDeserializeAndSet(BeanPropertyMap.java:285)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:248)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136)
at com.fasterxml.jackson.databind.ObjectReader._bind(ObjectReader.java:1408)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:858)
at com.fasterxml.jackson.jaxrs.base.ProviderBase.readFrom(ProviderBase.java:777)
At 2pm, it will be 48 hours I'm on it. Impossible to know why the mapper doesn't try to desiarilize with the deserializer and it fallback on the string. And guess what ... The fasterxml wiki is down ... Any clue is welcome.
--- EDIT ---
For information, the entity sent in request look like :
#XmlRootElement
public class XxxOutput {
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public static class XxxJson {
/* ---------- Properties ---------- */
public BigInteger id;
public String name;
public String definition;
public ZonedDateTime beginDate;
public ZonedDateTime endDate;
public ZonedDateTime creationDate;
public ZonedDateTime lastUpdate;
public Map<String, BigInteger> relateds;
/* ---------- Constructor ---------- */
public XxxJson() {}
public XxxJson(... all args ...) {
this.id = id;
...
this.relateds = relateds;
}
/* ---------- Useful methods ---------- */
public XxxJson addRelated(final Xxx related) {
...
// add the Xxx.id to the list
...
}
#Override
public String toString() {
return id.toString();
}
}
}