I was able to parse a JSON where Instant field is "2019-08-28T15:15:44.183Z"
First of all, ensure you have a dependency. E.g. in build.gradle
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
Then configure ObjectMapper to use this dependency and not (de)serialize Instant as timestamps that it does by default. I setup my ObjectMapper as:
ObjectMapper mapper = new ObjectMapper()
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Answer from Manos Nikolaidis on Stack OverflowYou need jackson dependency for this serialization and deserialization.
Add this dependency:
Gradle:
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4")
Maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
After that, You need to tell Jackson ObjectMapper to use JavaTimeModule. To do that, Autowire ObjectMapper in the main class and register JavaTimeModule to it.
import javax.annotation.PostConstruct;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@SpringBootApplication
public class MockEmployeeApplication {
@Autowired
private ObjectMapper objectMapper;
public static void main(String[] args) {
SpringApplication.run(MockEmployeeApplication.class, args);
}
@PostConstruct
public void setUp() {
objectMapper.registerModule(new JavaTimeModule());
}
}
After that, Your LocalDate and LocalDateTime should be serialized and deserialized correctly.
Spring Boot 2.2.2 / Gradle:
Gradle (build.gradle):
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
Entity (User.class):
LocalDate dateOfBirth;
Code:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
User user = mapper.readValue(json, User.class);
Where does this data come from? Is it written by your application?
It seems that the used Jackson mapper does not have the jackson-datatype-jsr310 module registered.
On reading the data tries to find a constructor of Instant that can be used to create an Instant object. But Instant does not have a default constructor and the Mapper should use the Instant.ofEpochSecond(long, long) method. This page pretty declares the problem and shows how the Jackson Mapper is configured.
Storing an instant in this way, as an object with two properties, is not the right way for storing dates in Elasticsearch. You should read the Elasticsearch documentation about how Elastcisearch handles date/time fields. When storing the instant as an object like this, you loose the ability to use Elasticsearch queries with criteria based on a date/time.
Which version of Spring Data Elasticsearch do you use? Because of problems like this, from the upcoming version 4.0 on, Spring Data Elasticsearch will not use the Jackson mapper anymore for entity mapping. The MappingElasticsearchConverter supports the use of the Elasticsearch date format and the java.time classes.
Well if I'm not completely wrong, your mapping fails due to the wrong format. The json you get looks like this:
"createdOn": {
"nano": 877000000,
"epochSecond": 1579861613
},
"changedOn": {
"nano": 920000000,
"epochSecond": 1579861613
},
That means you have 2 objects 'createdOn' and 'changedOn' with two properties (nano, epochSecond), while you try to map it to one object containing two properties named 'createdOn' and 'changedOn'. You need to modify that, you have e.g. a class called Entry, with two properties (nano, epochSeconds) and then a class with two properties (createdOn, changedOn) of type Entry
Following worked for me in spring-hateoas 1.1.2.RELEASE
private static RestTemplate getRestTemplate() {
List<HttpMessageConverter<?>> httpMessageConverters = SpringFactoriesLoader.loadFactories(TraversonDefaults.class,
Traverson.class.getClassLoader()).get(0).getHttpMessageConverters(Collections.singletonList(MediaTypes.HAL_JSON));
Optional<HttpMessageConverter<?>> first = httpMessageConverters.stream().filter(i -> i instanceof MappingJackson2HttpMessageConverter)
.findFirst();
if (first.isPresent()) {
MappingJackson2HttpMessageConverter httpMessageConverter = (MappingJackson2HttpMessageConverter) first.get();
httpMessageConverter.getObjectMapper().registerModule(new JavaTimeModule());
}
return new RestTemplateBuilder().messageConverters(httpMessageConverters).build();
}
and then using it:
Traverson traverson = new Traverson(URI.create("http://localhost:8080"), MediaTypes.HAL_JSON);
traverson.setRestOperations(getRestTemplate());
After some investigation i found a solution that works for me. The background is that the traverson client registers a MappingJackson2HttpMessageConverter with contains the Jackson2HalModule. To fix the problem i had to register also the JavaTimeModule.
I did the following
RestTemplateBuilder genericBuilder = this.restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(configuration.getSecurityRestConnectTimeout()))
.setReadTimeout(Duration.ofMillis(configuration.getSecurityRestReceiveTimeout()));
// my normal restTemplate
RestTemplateBuilder restTemplate = genericBuilder
.defaultMessageConverters()
.build();
// HAL specific restTemplate
RestTemplateBuilder restTemplateHal = genericBuilder
.messageConverters(getHalConverter(Arrays.asList(MediaTypes.HAL_JSON)))
.build();
The HalConverter is generated like this (registering also JavaTimeModule):
private static HttpMessageConverter<?> getHalConverter(List<MediaType> halFlavours) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(mapper);
converter.setSupportedMediaTypes(halFlavours);
return converter;
}
Then you can use Traverson in setting the just generated restTemplateHal
Traverson traverson = new Traverson(uri, MediaTypes.HAL_JSON);
traverson.setRestOperations(restTemplateHal);
MyClass myclass = traverson.follow().toObject(MyClass.class);
I remember I did not have this problem with older versions of spring (or maybe I was LUCKY) But this is how I solved it in Spring boot 2.1.7.RELEASE:
First, add Jackson's support modules in order to support Java 8 features (TimeDate API)
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Then register a default ObjectMapper bean with custom configurations (to support Java 8) in the Spring.
@Bean
@Primary
public ObjectMapper geObjMapper(){
return new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
}
NOTE: @Primary is used as a precautionary measure, so if there are other ObjectsMapper beans on the class-path, the spring will pick this one by default.
The solution you wrote is right approach. Other approaches are mentioned below. public class LocalDateTimeSerializer implements JsonSerializer { @Override public JsonElement serialize(LocalDateTime localDateTime, Type type, JsonSerializationContext jsonSerializationContext) { Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); Date date = Date.from(instant); return new JsonPrimitive(date.getTime()); } }
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer());
Gson gson = gsonBuilder.create();
Another approach. Any way try this . try to change date time format to string. "2011-04-08T09:00:00". The nano and other format are so complex I cannot tell from your lastseen json what datetime exactly you are talking about. It misses Time zone too so your local time can be anywhere you deploy solution,If you deploy in 3 different time zone machines local time is not correct. Use this string format or make your own "2011-04-08T09:00:00"
public class CustomJsonDateDeserializer extends JsonDeserializer<Date>
{
@Override
public Date deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
String date = jsonParser.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
@JsonDeserialize(using = CustomJsonDateDeserializer.class)
As @PaulSamsotha pointed out, I need to register the JacksonJsonProvider on the client side too:
ClientConfig cc = new DefaultClientConfig();
cc.getSingletons().add(ShopApplication.newJacksonJsonProvider());
wr = Client.create(cc).resource(Main.BASE_URI.resolve("products"));
which contains the customized solution:
private static JacksonJsonProvider newJacksonJsonProvider() {
ObjectMapper mapper =
new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule()); // new module, NOT JSR310Module
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setDateFormat(new StdDateFormat());
return new JacksonJsonProvider(mapper);
}
I was able to deserialize a JSON file with a ZonedDateTime with this code.
ObjectMapper objectMapper = new ObjectMapper()
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.registerModule(new JavaTimeModule())
MyObject myObject = objectMapper.readValue(new File("src/test/resources/data/my_object.json"), MyObject.class)