I am using Spring Boot and it has it's own Jackson's default serialization.
It works not correctly in scope of my task.
So I want override Jackson's default serialization with my own custom serializator.
Here is my code:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder() {
#Override
public void configure(ObjectMapper objectMapper) {
super.configure(objectMapper);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeCustomSerializer());
objectMapper.registerModule(simpleModule);
objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
}
}.serializerByType(ZonedDateTime.class, new JsonSerializer<ZonedDateTime>() {
#Override
public void serialize(ZonedDateTime value,
JsonGenerator gen,
SerializerProvider serializerProvider) throws IOException {
gen.writeString(value.getNano() + "");
}
});
}
private class ZonedDateTimeCustomSerializer extends JsonSerializer<ZonedDateTime> {
#Override
public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.getNano() + "");
}
}
As you can see I tried some cases such as
registering custom serializator through SimpleModule
overriding Jackson2ObjectMapperBuilder#serialize
Please tip me how to override default Jackson serializator
#Slowpokebreakingnews
If point is to add custom serializers - how about to customize Jackson2ObjectMapperBuilde by Jackson2ObjectMapperBuilderCustomizer:
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer()
{
return new Jackson2ObjectMapperBuilderCustomizer()
{
#Override
public void customize(Jackson2ObjectMapperBuilder builder)
{
builder.serializerByType(CustomPojo.class,
new CustomSerializator<CustomPojo>());
//affects to all dates in all pojos (I hope :) )
builder.indentOutput(true).dateFormat(new SimpleDateFormat
("yyyy-MM-dd'T'HH:mm:ssXXX"));
}
};
}
For without-Spring-boot-configuration I override configureMessageConverters() :
#Configuration
#EnableWebMvc
...
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
Jackson2ObjectMapperBuilder builder = new CustomObjectMapperBuilder();
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
and define my CustomJackson2ObjectMapperBuilder:
public class CustomObjectMapperBuilder extends Jackson2ObjectMapperBuilder
{
#Override
public void configure(ObjectMapper objectMapper)
{
super.configure(objectMapper);
SimpleModule module = new SimpleModule();
module.addSerializer(Selective.class, new SelectiveSerializer());
objectMapper.registerModule(module)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
}
}
For serializing date format you can do something like this
#Component
public class JsonDateSerializer extends JsonSerializer<Date>{
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");//yuor desired format goes here
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
You can do like your bean too.
And you have another option doing in your POJO like this
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
Related
I need to validate LocalDate fields in json requests. What i want is to prevent deserializing numbers as miilis to LocalDate. Here is example:
I have an entity:
public class Test {
#NotNull
#JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
//getter and setter of course
}
Jackson2ObjectMapperBuilder config:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
builder.featuresToEnable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
builder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
builder.modulesToInstall(new JavaTimeModule());
return builder;
}
Now if i'm receiveing:
{
"birthDate": 1
}
the result is birthDate=1970-01-02
I'm able to do so by setting leniency to false:
objectMapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forLeniency(false));
objectMapper.configOverride(LocalDateTime.class).setFormat(JsonFormat.Value.forLeniency(false));
And then it's working by throwing MismatchedInputException
But it's a little brutal to backward compatibility of our service, because we need to change all our date patterns from "yyyy-MM-dd" to "uuuu-MM-dd" and i wonder is there some solution to say jackson "If you see numbers or anything different from the pattern while deserialization, throw an exception"
You could write a custom LocalDateDeserializer:
public class MyLocalDateDeserializer extends JsonDeserializer<LocalDate> implements ContextualDeserializer {
private LocalDateDeserializer defaultDeserializer = new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
public MyLocalDateDeserializer() {
super();
}
public MyLocalDateDeserializer(LocalDateDeserializer defaultDeserializer) {
super();
this.defaultDeserializer = defaultDeserializer;
}
#Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
if (StringUtils.isNumeric(parser.getText())) {
throw JsonMappingException.from(parser, "Not a String representation of Date ");
}
return defaultDeserializer.deserialize(parser, context);
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
return (format == null) ? this : new MyLocalDateDeserializer(new LocalDateDeserializer(DateTimeFormatter.ofPattern(format.getPattern())));
}
protected JsonFormat.Value findFormatOverrides(DeserializationContext ctxt,
BeanProperty prop, Class<?> typeForDefaults)
{
if (prop != null) {
return prop.findPropertyFormat(ctxt.getConfig(), typeForDefaults);
}
// even without property or AnnotationIntrospector, may have type-specific defaults
return ctxt.getDefaultPropertyFormat(typeForDefaults);
}
}
and register it when needed.
Here my simple Tests:
#Test()
public void testObjectMapperForLocalDate() throws IOException {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new MyLocalDateDeserializer());
builder.modulesToInstall(javaTimeModule);
ObjectMapper objectMapper = builder.build();
DateContainer container = objectMapper.readValue("{\r\n" +
" \"birthDate\": \"1999-01-01\"\r\n" +
"}", DateContainer.class);
System.out.println(container.getBirthDate());
}
#Test()
public void testFailObjectMapperForLocalDate() throws IOException {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new MyLocalDateDeserializer());
builder.modulesToInstall(javaTimeModule);
ObjectMapper objectMapper = builder.build();
assertThrows(JsonMappingException.class, () -> {
DateContainer container = objectMapper.readValue("{\r\n" +
" \"birthDate\": 1\r\n" +
"}", DateContainer.class);
System.out.println(container.getBirthDate());
});
}
EDIT
Deserializer uses Pattern
We need customize global configuration that describes the setting cors-header for requests.
I use SpringBoot and a project is spring bootpackaged in file extention *.war.
#SpringBootConfiguration
#EnableScheduling
#SpringBootApplication
public class App
extends SpringBootServletInitializer
implements WebApplicationInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger( App.class );
public static void main(String[] args) {
LOGGER.info("Start an application...");
SpringApplication.run(App.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
LOGGER.info("There is building the web application!");
return builder.sources(App.class);
}
}
This is the settitng for cors-header.
I must import the Spring MVC configuration (#EnableWebMvc), that the configuration that i have set work.
#EnableWebMvc
#Configuration
public class CorsGlobalConfiguration {
private final static String ROOT_API = "/**";
#Bean
public WebMvcConfigurer corsConfig(){
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(ROOT_API)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("GET","POST","PUT");
}
};
}
}
But a Client have gotten a json in date that come in array.
For exmaple:
A client must get:
"2020-03-14T11:32:33",
But a client is getting
[2020, 03, 14, 11, 32, 33]
Update_1
I did.
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper configureObjectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(javaTimeModule);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
}
It don't work.
Update_2
I did.
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
It don't work.
Update_3
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.10.3</version>
</dependency>
and
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
It also don't work.
Update_4
I performed the following actions;
application.properties
#spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
#Component
public class JacksonLocalDateSerializer extends StdSerializer<LocalDateTime> {
private static final long serialVersionUID = -7880057299936771237L;
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSZ")
.withResolverStyle(ResolverStyle.STRICT);
public JacksonLocalDateSerializer(Class<LocalDateTime> t) {
super(t);
}
public JacksonLocalDateSerializer() {
this(null);
}
#Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(formatter.format(value));
}
}
#Configuration
public class JacksonConfig {
private JacksonLocalDateSerializer jacksonLocalDateSerializer;
#Autowired
public JacksonConfig(JacksonLocalDateSerializer jacksonLocalDateSerializer) {
this.jacksonLocalDateSerializer = jacksonLocalDateSerializer;
}
#Bean
#Primary
public ObjectMapper configureObjectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, this.jacksonLocalDateSerializer);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(javaTimeModule);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
}
dto
...
private LocalDateTime statusDate;
...
I get:
Why is it so?
How do it correct?
Who has any ideas ?
Update_5
I refused to use the #EnableWebMvc annotation.
My the configuration class is now:
#Configuration
public class CorsGlobalConfiguration {
#Value("${api.prefix}")
private String apiPrefix;
#Value("${header.cors.origins.allow}")
private String [] headerCorsOriginsAllow;
#Value("${header.cors.methods.allow}")
private String [] headerCorsMethodsAllow;
#Bean
public WebMvcConfigurer corsConfig() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(apiPrefix)
.allowedOrigins(headerCorsOriginsAllow)
.allowedMethods(headerCorsMethodsAllow);
}
};
}
}
The problem had solved.
Add jackson-datatype-jsr310 to your dependencies.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId></artifactId>
<version>2.9.7</version>
</dependency>
Then add below property to your application.properties file:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
If you add #EnableWebMvc then by default SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is enabled
So, configure the Jackson Object Mapper manually to disable this.
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper configureObjectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(javaTimeModule);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
}
or
change it in application.properties
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
You can also add your own Serializer class using this line (e.g JacksonLocalDateSerializer)
javaTimeModule.addSerializer(LocalDate.class, new JacksonLocalDateSerializer());
Example : Here I need show as format yyyy-MM-dd HH:mm:ss
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));
}
}
Assume that I have the following class:
class MyClass {
private Date dateToBeCustomeSerialized;
private Date dateToBeDefaultSerialized;
}
In my Spring Boot application I need to serialize every Date object to a String. For that purpose and in order to avoid inserting #JsonSerializer everywhere, I have introduced a custom Serializer at application level.
The problem is that I want to avoid using the custom serializer on fields with name (not value) dateToBeDefaultSerialized and I cannot seem to find a way to do this.
This is how my custom "global" Date Serializer looks like:
#Bean
#Primary
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new DateSerializer());
objectMapper.registerModule(module);
return objectMapper;
}
public class DateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(value);
gen.writeString(date);
}
}
Is there anyway to serialize properties of name dateToBeDefaultSerialized using the default serializer?
It's possible to get the name of currently serialized field by implementing ContextualSerializer. The default serialization is available through SerializerProvider. Try rewriting the serializer like this:
class DateSerializer extends JsonSerializer<Date> implements ContextualSerializer {
private boolean doCustom;
DateSerializer() {}
private DateSerializer(boolean doCustom) { this.doCustom = doCustom; }
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (doCustom) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(value);
gen.writeString(date);
} else {
provider.defaultSerializeDateValue(value, gen);
}
}
#Override
public JsonSerializer<Date> createContextual(SerializerProvider config, BeanProperty property) {
boolean doCustom = property == null || !"dateToBeDefaultSerialized".equals(property.getName());
return new DateSerializer(doCustom);
}
}
I am using fasterxml jackson for json serialization. I have written date serializer as
public class DateObjectSerializer extends JsonSerializer<Date> {
public static final String DATE_FORMAT = "dd.MM.yyyy";
#Override
public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
System.out.println("From DateObjectSerializer");
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
String formattedDate = dateFormat.format(date);
jgen.writeString(formattedDate);
}
}
But it is not being invoked. However other Jackson Serializers are working fine.
So I added following configuration in application.yaml
spring:
jackson:
serialization-inclusion: non_null
date-format: dd.MM.yyyy
But it din't work.
So I have added this code in SpringBootConfiguration class.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false).setDateFormat(dateFormat);
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
Now dates are being serialized correctly. But now valid JSON equivalent strings are not being transformed to JSON as mentioned here.
#RestController
public class SampleController {
#RequestMapping(value = "/jsonInfo", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE)
public String jsonInfo() {
String string = "{\"name\": \"foo\"}"
return string;
}
}
Try this
import com.fasterxml.jackson.databind.ObjectMapper;
:
#Autowired
private ObjectMapper objectMapper;
#RestController
public class SampleController {
#RequestMapping(value = "/jsonInfo", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE)
public JsonNode jsonInfo() throws JsonProcessingException, IOException {
String string = "{\"name\": \"foo\"}"
return objectMapper.readTree(string);
}
}
I need to set format for class' date serialization.
I have the version of Jackson, which doesn't have #JsonFormat. That's Why I wrote custom class:
public class CDJsonDateSerializer extends JsonSerializer<Date>{
#Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
String dateString = dateFormat.format(date);
jsonGenerator.writeString(dateString);
}
}
And used it:
#JsonSerialize(using = CDJsonDateSerializer.class)
private Date startDate;
But, I have another fields which have different date's formats and I don't want to create another classes for serialization. Can I add all needed formats like constants to CDJsonDateSerializer class and set needed format with annotation #JsonSerialize ?
Something like this:
#JsonSerialize(using = CDJsonDateSerializer.class, CDJsonDateSerializer.FIRST_FORMAT).
AFTER THE ANSWER BELOW:
It works after some corrections. I've changed the way of getting annotation in createContextual method:
#Override
public JsonSerializer createContextual(SerializationConfig serializationConfig, BeanProperty beanProperty) {
return new CustomDateSerializer(beanProperty.getAnnotation(JsonDateFormat.class).value());
}
And I've added #JacksonAnnotation to my created new annotation JsonDateFormat:
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface JsonDateFormat {
String value();
}
If you cannot use #JsonFormat from Jackson 2, I'd recommend you to introduce your own custom annotation which will contain the format field. Your serailizer should then implement the ContextualSerializer interface to get access to the annotation value.
Here is an example for Jackson 1.9.X:
public class JacksonDateFormat {
#Retention(RetentionPolicy.RUNTIME)
public static #interface MyJsonFormat {
String value();
}
public static class Bean {
#MyJsonFormat("dd.MM.yyyy") #JsonSerialize(using = MyDateSerializer.class)
public final Date date1;
#MyJsonFormat("yyyy-MM-dd") #JsonSerialize(using = MyDateSerializer.class)
public final Date date2;
public Bean(final Date date1, final Date date2) {
this.date1 = date1;
this.date2 = date2;
}
}
public static class MyDateSerializer extends JsonSerializer<Date>
implements ContextualSerializer {
private final String format;
private MyDateSerializer(final String format) {this.format = format;}
public MyDateSerializer() {this.format = null;}
#Override
public void serialize(
final Date value, final JsonGenerator jgen, final SerializerProvider provider)
throws IOException {
jgen.writeString(new SimpleDateFormat(format).format(value));
}
#Override
public JsonSerializer createContextual(
final SerializationConfig serializationConfig, final BeanProperty beanProperty)
throws JsonMappingException {
final AnnotatedElement annotated = beanProperty.getMember().getAnnotated();
return new MyDateSerializer(annotated.getAnnotation(MyJsonFormat.class).value());
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final Bean value = new Bean(new Date(), new Date());
System.out.println(mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(value));
}
}
Output:
{
"date1" : "02.12.2014",
"date2" : "2014-12-02"
}
If you have access to the ObjectMapper you can register your custom serializer for all the Date types, so you want longer need to put #JsonSerialize annotation.
Here is an example:
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("", Version.unknownVersion());
module.addSerializer(Date.class, new MyDateSerializer(null));
mapper.registerModule(module);