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.
Related
I'm trying to figure how to explain Jersey and Jackson how to deserialize a Future that I pass as byte[].
I create my own ContextResolver
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private static ObjectMapper mapper = null;
public ObjectMapperContextResolver() {
mapper = ObjectMapperFactory.getObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
And the implementation of the ObjectMapper
public static ObjectMapper getObjectMapper() {
ObjectMapper defaultObjectMapper = new ObjectMapper();
SimpleModule futureModule = new SimpleModule("FutureModule");
futureModule.<Future>addDeserializer(Future.class, new FutureDeserializer<String>());
defaultObjectMapper.registerModule(futureModule);
return defaultObjectMapper;
}
And then finally in the implementation of my FutureDeserializer
public class FutureDeserializer<T> extends StdDeserializer<Future<T>>{
public FutureDeserializer() {
super(Future.class);
}
#Override
public Future<T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper=(ObjectMapper)jp.getCodec();
//TODO: Breakpoint never stop here
return null;
}
}
Then I register in my ResourceConfig before start the JerseyTest
ResourceConfig rc = new ResourceConfig();
rc.register(SpringLifecycleListener.class);
rc.register(RequestContextFilter.class);
rc.register(new JacksonFeature());
rc.register(new ObjectMapperContextResolver());
But when I run the test the ObjectMapperContextResolver is invoked and the mapper returned to Jersey, but he never use the FutureDeserializer.
Any idea what I´m doing wrong?
I have a Spring Boot web app with several #RestController classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization ), I have created a custom ObjectMapper:
#Configuration
public class Config{
#Bean
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper:
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper bean for its own internal workings?
The Simone Pontiggia answer is in the correct direction. You should create one #Primary bean, which Spring will use in its internals, and then to create your own ObjectMapper beans and autowired them using #Qualifier.
The problem here is that, creating default bean like:
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Won't actually work as expected, because the Spring default ObjectMapper has additional configurations.
The correct way to create default ObjectMapper that will be used by spring, is:
#Bean
#Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
You can find more information about the Spring default ObjectMapper here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper
Since I didn't want to touch Spring's default ObjectMapper, creating a #Primary ObjectMapper to shadow Spring's default ObjectMapper was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor which registers in Spring's context a custom, non primary ObjectMapper:
#Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
.getBeanDefinition();
// Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
// as primary
beanDefinition.setPrimary(false);
final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
beanDefinition.addQualifier(mapperQualifier);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
}
private ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper bean.
My qualifier is an annotation which is annotated with #Qualifier:
#Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper using my custom annotation, like this:
#Repository
public class MyDao {
#Autowired
public MyDao(DataSource dataSource, #PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}
You can provide a standard ObjectMapper and your customized object mapper, and set the standard as #Primary.
Then gives your custom ObjectMapper a name and use it with #Qualifier annotation.
#Configuration
public class Config{
//This bean will be selected for rest
#Bean
#Primary
public ObjectMapper stdMapper(){
return new ObjectMapper();
}
//You can explicitly refer this bean later
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
Now you can reference your custom mapper
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
#Qualifier("customObjectMapper")
ObjectMapper objectMapper
//Dao implementation...
}
#Resource("custonmObjectMapper") will do the same of #Autowired and #Qualifier together
You can create:
public class MapperUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(byteArrray, parseType);
}
}
ObjectMapper is thread-safe. However, some people discourage having single instance because of performance issues (Should I declare Jackson's ObjectMapper as a static field? ).
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();
}
}
I have a jersey2 application configured for JSON support via Jackson, adding
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
in the POM file and
public MyApplication() {
...
register(JacksonFeature.class)
...
}
in my application. Everything works, my resources get deserialized POJOs as arguments
#POST #Consumes(MediaType.APPLICATION_JSON)
public void blah(MyPojo p) {
...
}
Now one of thoese resources needs a reference to Jackson's ObjectMapper to do some deserialization on its own. I've tried doing something like
#Inject
public MyResource(#Context ObjectMapper mapper) {
...
}
or
#GET
public String foo(#Context ObjectMapper mapper) {
...
}
but in both cases the reference to mapper is null. How can I inject a reference to the ObjectMapper in my resources?
First there is no default ObjectMapper used by the Jackson provider. It doesn't use an ObjectMapper at all actually. It makes use of other Jackson APIs to handle the (de)serialization.
If you want to use/inject a single ObjectMapper instance, then you should just create a Factory for it
public class ObjectMapperFactory implements Factory<ObjectMapper> {
final ObjectMapper mapper = new ObjectMapper();
#Override
public ObjectMapper provide() {
return mapper;
}
#Override
public void dispose(ObjectMapper t) {}
}
Then bind it
register(new AbstractBinder(){
#Override
public void configure() {
bindFactory(ObjectMapperFactory.class)
.to(ObjectMapper.class).in(Singleton.class);
}
});
One thing should be noted is that any configuration of the ObjectMapper is not thread safe. So say you tried to configure it from your resource method, those operations are not thread safe.
Another thing to note with the Jackson provider, is that if we provide a ContextResolver, like mentioned by #Laurentiu L, then the Jackson provider will switch to using our ObjectMapper. In which case, if you want to use that same ObjectMapper, then you can look it up in the Factory. For example
public class ObjectMapperFactory implements Factory<ObjectMapper> {
private final Providers providers;
final ObjectMapper mapper = new ObjectMapper();
public ObjectMapperFactory(#Context Providers providers) {
this.providers = providers;
}
#Override
public ObjectMapper provide() {
ContextResolver<ObjectMapper> resolver = providers.getContextResolver(
ObjectMapper.class, MediaType.APPLICATION_JSON);
if (resolver == null) { return mapper; }
return resolver.getContext(null);
}
#Override
public void dispose(ObjectMapper t) {}
}
For the above to work (use a single ObjectMapper), you need to make sure to implement the ContextResolver<ObjectMapper>, and make sure to annotation the ContextResolver with the corresponding #Produces and #Consumes media types.
Aside from the JacksonFeature you need to register a ContextResolver for ObjectMapper.
Simple example from the Documentation at 9.1.4.2. Configure and register
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(Feature.INDENT_OUTPUT, true);
return result;
}
// ...
}
Complete code example
available on Github
You will also need to register it
.register(MyObjectMapperProvider.class)
I've got a class that configures a Jackson ObjectMapper. It adds in some custom serializers and deserializers for my object types as follows:
public class JsonMapperFactory {
public static ObjectMapper createObjectMapper() {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
addCustomDeserializersTo(module);
addCustomSerializersTo(module);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
private static void addCustomSerializersTo(final SimpleModule module) {
module.addSerializer(DateTime.class, new DateTimeSerializer());
}
private static void addCustomDeserializersTo(final SimpleModule objectMapper) {
objectMapper.addDeserializer(DateTime.class, new DateTimeDeserializer());
}
}
I've tested my customer serializers within their own test classes, so in my test of this JsonMapperFactory class, I'm trying to simply check that the ObjectMapper created has the expected serializers (or deserializers) This could be achieve by introspecting the ObjectMapper, but it doesn't seem to have any mechanisms to do this.
Does anyone know of a nice way to test that?
For deserializers, I have the following:
private void assertThatObjectMapperUsesCorrectDeserializer(final Class<?> typeClazz, final Class<?> deserializerClazz) throws JsonMappingException {
final DeserializationConfig deserializationConfig = this.objectMapper.getDeserializationConfig();
final JsonDeserializer<Object> deserializer = this.objectMapper.getDeserializerProvider().findTypedValueDeserializer(deserializationConfig, javaTypeFor(typeClazz), null);
assertThat(deserializer, is(instanceOf(deserializerClazz)));
}
private JavaType javaTypeFor(final Class<?> clazz) {
return TypeFactory.type(clazz); //deprecated method :(
}
Which is quite verbose and uses deprecated methods.
I'm yet to find a way to do a similar test for the serializers. So I've currently resorted to serializing an object and check it serializes correctly (essentially duplicating the serializer test)
Any ideas are very welcome.
From the answers & comments provided here, I recently redesigned the class to use builders for both the Module and the ObjectMapper. This allowed me to provide mocks and check that the correct (de)serializers were added to the module and then the module is registered to the object mapper as expected.
Object Mapper Builder:
public class ObjectMapperBuilder {
ObjectMapper mapper;
public ObjectMapperBuilder configure(final ObjectMapper mapper) {
this.mapper = mapper;
return this;
}
public ObjectMapperBuilder withModule(final Module module) {
this.mapper.registerModule(module);
return this;
}
public ObjectMapper build() {
return this.mapper;
}
}
Module Builder:
public class SimpleModuleBuilder {
SimpleModule module;
public SimpleModuleBuilder configure(final SimpleModule module) {
this.module = module;
return this;
}
public <X> SimpleModuleBuilder withSerializer(final Class<X> clazz, final JsonSerializer<X> serializer) {
this.module.addSerializer(clazz, serializer);
return this;
}
public <X> SimpleModuleBuilder withDeserializer(final Class<X> clazz, final JsonDeserializer<X> deserializer) {
this.module.addDeserializer(clazz, deserializer);
return this;
}
public SimpleModule build() {
return this.module;
}
}
And finally, the new JsonMapperFactory:
public class JsonMapperFactory {
public static ObjectMapper configureObjectMapper(final ObjectMapper mapper, final SimpleModule module) {
final SimpleModuleBuilder modulebuilder = new SimpleModuleBuilder();
final SimpleModule configuredModule = modulebuilder.configure(module)
.withSerializer(DateTime.class, new DateTimeSerializer())
.withDeserializer(DateTime.class, new DateTimeDeserializer())
.build();
final ObjectMapperBuilder objectMapperBuilder = new ObjectMapperBuilder();
return objectMapperBuilder.configure(mapper).withModule(configuredModule).build();
}
}
The factory method is still used within Spring configuration, but the configuration now instantiates the blank Module and ObjectMapper before providing them to the factory methods that then configure them.
If JsonDeserializer (and DateTimeDeserializer too) was an interface, you could easily "JMock" it, pass mocked instance to JsonMapperFactory#createObjectMapper and then expect exactly 1 invocation of your custom "serialize" method; e.g.
DateTimeSerializer serializer = context.mock(DateTimeSerializer.class);
DateTimeDeserializer serializer = context.mock(DateTimeDeserializer.class);
ObjectMapper mapper = JacksonMapperFactory.createObjectMapper(deserializer, serializer);
exactly(1).of(jsonDeserializer).serialize(myDateTime,
with(any(JsonGenerator.class),
with(any(SerializerProvider.class)))
Being a concrete class, you can instead define a new (test-scoped) De/Serializer that extends your custom DateTime(De)serializer, and simply count invocation on that:
private static class DateTimeDeserializerWithCounter extends DateTimeDeserializer {
public int counter = 0;
#Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
counter++;
return super.deserialize(jsonParser, deserializationContext);
}
}
#Test
public void usageTest(){
//init mapper with the above DateTimeDeserializerWithCounter - see below
mapper.readValue("...", DateTime.class);
Assert.assertEquals(1, deserializer.counter);
}
Below a snapshot of a more "test-oriented" Factory:
//package visibility, to allow passing different De/Serializers while testing
static ObjectMapper createObjectMapper(JsonDeserializer deserializer, JsonSerializer serializer) {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
module.addDeserializer(DateTime.class, deserializer);
module.addSerializer(DateTime.class, serializer);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
//production method: no-args, as in the original version
public static ObjectMapper createObjectMapper() {
return createObjectMapper(new DateTimeDeserializer(), new DateTimeSerializer());
}
Hope that helps.