I don't have option of spring.xml so i went by annotated method.
I have below REST Interfaces in package : com.dpk.cm.impl.ecommerce.rest
and implementation in com.dpk.cm.impl.ecommerce.rest.services
I created one spring config class: but seems like i am still seeing in my JSON response empty objects.
Below is my code :
#Configuration
#ComponentScan(basePackages = "com.dpk.cm.impl.ecommerce.rest")
#EnableWebMvc
public class SpringConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setSerializationInclusion(Inclusion.NON_EMPTY);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
}
How to remove the Empty Objects from the JSON Reponse Object.
I had similar requirement, but though I use CXF framework on spring boot, there spring boot was creating ObjectMapper which was overriding configuration. Hence I was manually create ObjectMapper as shown below.
#Bean(name = "objectMapper")
public ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(
SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, false);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
true);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
You can create your custom serializer where you can add a condition on serialization of the object.
Model
#JsonSerialize(using = IgnoreEmptyPersonSerializer.class)
public class Person {
private String name;
private String address;
public Person(String name, String address){
this.name = name;
this.address = address;
}
...
//setters and getters
...
}
Custom Serializer
public class IgnoreEmptyPersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String name = value.getName();
String address = value.getAddress();
//Dont serialize it if it is empty
if((name == null || name.trim().equals("")) &&
(address == null || address.trim().equals(""))){
return;
}
jgen.writeStartObject();
jgen.writeFieldName("name");
jgen.writeString(value.getName());
jgen.writeFieldName("address");
jgen.writeString(value.getAddress());
jgen.writeEndObject();
}
}
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
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);
}
}
When I'm trying to deserialize Object from a String and this String does not contain certain fields or has fields that are not in my Object, Jackson serializer is completely okay with that and just creates my Object with null/Optional.empty() fields, also ignoring unkown properties. I tried to set reader with feature FAIL_ON_UNKNOWN_PROPERTIES but to no success. I have fairly simple Jackson configuration, not much besides adding support for Java 8 and java.time.
Edit:
public final ObjectReader reader;
public final ObjectWriter writer;
private JsonMapperTestInstance() {
ObjectMapper mapper = new JacksonConfiguration().objectMapper();
reader = mapper.reader();
writer = mapper.writer().withFeatures(SerializationFeature.INDENT_OUTPUT);
}
public <T> T deserialize(Class<T> actual, String serialized) throws IOException {
return reader.forType(actual).readValue(serialized);
}
JacksonConfiguration:
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
registerModules(mapper);
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return mapper;
}
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8Module() {
return new Jdk8Module().configureAbsentsAsNulls(true);
}
private void registerModules(ObjectMapper mapper) {
mapper.registerModule(jdk8Module());
mapper.registerModule(javaTimeModule());
}
#Primary
#Bean
public ObjectWriter writer(ObjectMapper mapper) {
return mapper.writer();
}
#Primary
#Bean
public ObjectReader reader(ObjectMapper mapper) {
return mapper.reader();
}
I have determined that annotation #JasonUnwrapped is causing this behaviour. Without it Jackson throws expection on property "very_wrong_field", which previously was silently ignoring.
I am trying to enable Jackson's "fail on unknown properties" feature for all endpoints in my service. I've added Jackson2ObjectMapperBuilder:
#Configuration
public class JacksonConfig{
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder(){
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.failOnUnknownProperties(true);
return builder;
}
}
When I use injected ObjectMapper, validation works as supposed, it throws HttpMessageNotReadableException while passing invalid json:
public class Person{
#NotNull
private String name;
#NotNull
private String surname;
/*getters and setters*/
}
#Autowired
private ObjectMapper objectMapper;
Person person = objectMapper.readValue("{ "\unknown\":\"field\", \"name\":\"John\", \"surname\":\"Smith\" }", Person.class);
However when i pass same json straight to controller validation does not occur and body of method is invoked:
#RequestMapping(value = "/something", method = RequestMethod.POST)
public void something(#Valid #RequestBody Person person){...}
Firstly I thought that MessageConverter does not use custom ObjectMapper so I checked it:
#Autowired
private MappingJackson2HttpMessageConverter converter;
converter.getObjectMapper().getDeserializationConfig().hasDeserializationFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.getMask()));
//returns true
It's even more weird because when i use repositories in other part of service validation works in POST method but does not work in PUT method.
#RepositoryRestResource(collectionResourceRel = "...", path = "...")
public interface CarRepository extends CrudRepository<Car, Long> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN') || hasPermission(#car, T(...).CREATE_OR_MODIFY")
Car save(#Param("car") Car car);
/*other methods*/
}
Is there a simple way to force controllers and repositories to check if passed json does not contain unknown properties?
When working with spring data rest there are multiple ObjectMappers registered in the application context e.g. one for application/hal+json and one for application/json. It looks like spring data rest is not using the primary object mapper that you configured.
If you want to configure the spring data rest object mapper you can do this using a RepositoryRestConfigurerAdapter - see http://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.configuration
public class DefaultRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
if (this.objectMapperBuilder != null) {
this.objectMapperBuilder.configure(objectMapper);
}
}
}