Automatic add Root Element in JAXB - java

In Jersey, how can I automatically add root in JAXB pojo?
I have my JAXB pojo
class Product {
private String name;
....
}
By default the generated json is
{
"name": "Burton Custom Freestlye 151",
}
I would like it to be
{
"product": {
"name": "Burton Custom Freestlye 151",
}
}
Note: What I mean by automatic is not creating separate class just to enclose another pojo.
This should be in serialization/deserialization into JSON.
Also I have other JSON don't use root element.

With Spring Boot, you can just configure an ObjectMapper as a Spring bean, and Spring Boot is set up where Jersey will use the mapper. The configuration property for the ObjectMapper to automatically add a root element is
SerializationFeature.WRAP_ROOT_VALUE
The default behavior is to take the class name and lower case it. If you want something different you can annotation the class with #XmlRootElement("newName") or #JsonRootName("newName"). To make a Spring bean just add the following in your configuration class
#Bean
public ObjectMapper mapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_NAME, true);
return mapper;
}
Note I think this (creating a Spring bean for the ObjectMapper) is only available starting from 1.4.0. If you are using a earlier version of Boot, you should just do this.
Also if you are expecting incoming JSON with the wrapped value, you will also want to unwrap it. You can do that with
DeserializationFeature.UNWRAP_ROOT_VALUE
Configure it the same way as above (passing true).
UPDATE
(Not very useful at this point, but maybe in the future)
Looking at the source for #JsonRootName, it seems Jackson has been planning on adding an alwaysWrap property in the annotation since 2.4. But I guess they have been delaying this. Not sure why. Maybe in the future it'll show up.

Related

Spring beans usage inside library

I'm developing some libs for internal use in my company, which using spring and spring Boot.
I'm encountered a problem with my bean definition. I want to create some beans of objects that don't belong to my library, for example:
#Configuration
public class LibClass {
#Bean
public Gson gson() { return new Gson();}}
However, whenever I do this it affect the service that using my library and creating beans for them too, which means they will be forced to use my Gson or enabling overriding of beans, by defining spring.main.allow-bean-definition-overriding=true which feels wrong.
For example, if I have the same code as above in my service:
#Configuration
public class ServiceClass {
#Bean
public Gson gson() { return new Gson();}}
Spring throws
BeanDefinitionOverrideException
Is creating those kind of beans inside a library is a good practice?
How can I tell spring to use this bean just inside my libraries classes and not outside
Thanks
Edit:
As I mentioned in the comments, I don't want to force using of #Qualifier, as if they don't using it already, which we don't if there is no need to, they will get this exception and won't know why as it is not trivial to understand that some libraries using random beans. Nonetheless, if someone forget to create a bean, they can use the lib bean, by mistake, without even knowing. This could cause some nasty and unexpected behavior.
Also, the annotation #ConditionalOnMissingBean won't help me here, as I won't be able to configure specific configuration later if needed and my internal library will be affected by an unknown configuration from the user, which isn't seem like a good practice
changing the bean name and adding a qualifier should work:
#Bean
#Qualifier("gsonForInternal")
public Gson gsonForInternal() { return new Gson();}
where autowiring you should :
#Autowired
#Qualifier("gsonForInternal")
private Gson gsonForInternal;
Edit:
in case you want you can make this bean conditional:
#ConditionalOnMissingBean
#Bean
public Gson gson() { return new Gson();}
however if the library user will define its own bean:
#Bean
public Gson gson() { return new Gson();}
this means your library will use his defined Gson
so i think you should be using qualifier - if i understand your requirement correctly

ObjectMapper ignoring configuration when annotations are present

My project is using application.properties file to set the property as following: spring.jackson.deserialization.fail-on-unknown-properties=true, which works in all cases but one:
class Model {
#JsonUnwrapped
public SubModel subModel;
}
simply commenting out the annotation causes ObjectMapper to fail as intended, but as soon as the annotation is added, the option set seems to be ignored.
How can I configure jackson to use annotations along with the config?
Due to logic needed to pass down unwrapped properties from parent context, there is no way to efficiently verify which properties might be legitimately mapped to child POJOs (ones being unwrapped), and which not.
As of now it is not possible to make jackson to fail on unknown property with unwrapping.
Issue is still open ,
https://github.com/FasterXML/jackson-databind/issues/650
How can I configure jackson to use annotations along with the config?
It is nothing to do with config or annotations, they are working fine.

Spring Boot Autoconfigured ObjectMapper plus additional ObjectMapper

We are currently trying to implement a JSON Logging library making use of spring auto configuration or create its Jackson ObjectMapper. Our aim is to not override the spring auto configuration in class JacksonAutoConfiguration so that every customization by clients of the logging library won't be disabled.
The actual spring behavior is bean based and our main problem is that the JacksonProperties are not customizable and reusable for us. If we actually add a second bean of JacksonProperties the application start up would fail because JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.class won't be able to handle a second bean. (The Spring Boot internal one is not annotated as #Primary.)
So what we did was start reimplementing every bean like the builder, customizer and so on. But this is not very maintainable as it duplicates framework code.
Our question now is if there would be any way to adapt the way of creating data sources for jackson object mapper beans. An example of creating data sources would be a following one.
#Bean(name = "testDataSource")
#ConfigurationProperties(prefix = "test.datasource")
public HikariDataSource naeDataSource(DataSourceProperties testDataSourceProperties) {
return testDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
We know the problem would still be that overriding the object mapper would deactivate it but if you pay attention to the application context it would be much easier to offer multiple object mapper instances.
Or is there any easier or other way we did not find so far?
Versions used:
Spring-Boot 2.0.5.RELEASE
UPDATE
I forked the spring boot project, made some changes to Spring Boot Autoconfiguration module and also set up a small demo project. I do not think it is the perfect way but with this changes it would be possible to initialize own object mapper easily from configuration properties. For example you can now easily create five object mapper for five different rest templates and clients called via REST-API.
#ConfigurationProperties(prefix = "logging.jackson")
#Bean("loggingJacksonProperties")
public JacksonProperties loggingJacksonProperties() {
return new JacksonProperties();
}
#Bean
public ObjectMapper secondObjectMapper(#Qualifier("loggingJacksonProperties") JacksonProperties loggingJacksonProperties) {
return loggingJacksonProperties.initializeJackson2ObjectMapperBuilder().build();
}
Comparing-Fork: https://github.com/spring-projects/spring-boot/compare/2.1.x...mixaaaa:feature/jackson_properties_initializer
Demo-Project: https://github.com/mixaaaa/jackson-demo

Jackson serialization is not working after Spring Boot upgrade

Yesterday I started with upgrade from Spring Brussels-SR3 to Spring Brussels-SR6.
The Spring Boot goes from 1.5.4. to 1.5.9, Jackson goes from 2.8.8 to 2.8.10). I am using HATEOAS and HAL links. It means my Jackson configuration looks like this:
#Configuration
public class JacksonConfiguration {
private static final String SPRING_HATEOAS_OBJECT_MAPPER = "_halObjectMapper";
#Autowired
#Qualifier(SPRING_HATEOAS_OBJECT_MAPPER)
private ObjectMapper springHateoasObjectMapper;
#Primary
#Bean(name = "objectMapper")
ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_INSTANT));
springHateoasObjectMapper.registerModules(javaTimeModule);
springHateoasObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
springHateoasObjectMapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
springHateoasObjectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
springHateoasObjectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
springHateoasObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
springHateoasObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);
springHateoasObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return springHateoasObjectMapper;
}
}
It means I am reusing the _halObjectMapper bean and adding some more configurations. It had been working until I started with the upgrade.
What is wrong?
The thing that goes wrong is that after the upgrade all my serialization customizations and HAL conventions are not applied- datetime formats, indenting HAL "_links" JSON field changes to "links" ... So the _halObjectMapper is not used for serialization any more.
Any clue what could be the issue or where should I dig to figure out what is wrong?
Additional info after some debugging:
I have figured out that TypeConstrainedMappingJackson2HttpMessageConverter that uses _halObjectMapper is not used for conversion to json anymore. The reason is that it does not get to the collection of converters when starting spring. It looks like it is not created for RequestMappingHandlerAdapter bean because of some condition that skips creation when Jackson2HalModule is allready registered in some other converter (in this case ProjectingJackson2HttpMessageConverter).
Any idea what could be a cause or where to look at to figure out why the spring boot start proceeds differently?
Additional info after some more debugging:
The difference I see before and after the upgrade is that before the upgrade the ProjectingJackson2HttpMessageConverter was populated with new instance of ObjectMapper. But after the upgrade, the ObjectMapper is resolved from container so the _halObjectMapper is chosen. As a result the ProjectingJackson2HttpMessageConverter matches as a converter with registered halModule and TypeConstrainedMappingJackson2HttpMessageConverter creation is ommited for RequestMappingHandlerAdapter.
One more interesting thing is that there are two more microservices I upgraded. The difference is that one has the same issue and one is working. The one that is working has different spring security and oauth2 setup. Bean of class OAuth2RestTemplate is not defined in the microservice that is working. Microservices with OAuth2RestTemplate have the issue. The reason why I am pointing this out is that there is different in the initialization behavior in these two cases. The OAuth2RestTemplate rest template is populated with these converters too and it might affect the initialization process.
Temporary solution
As a temporary hotfix I have downgraded spring-data-commons from 1.13.6.RELEASE to 1.13.6.RELEASE. However the newer code makes more sense to me.
I am still trying to achieve some better understanding and figure out correct approach
I don't know if it is helpfull to you, but I had a very similar problem with a Spring Boot upgrade from Version 2.0.3 to 2.0.4. I still don't know what exactly caused the problem, but the solution was to create Beans for every Module I use instead of replacing the default ObjectMapper. In your case it would look something like this:
#Configuration
public class JacksonConfiguration {
#Bean
JavaTimeModule javaTimeModule () {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_INSTANT));
return javaTimeModule;
}
}
All Features can be set via the applications.properties file like this:
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=false
spring.jackson.deserialization.READ_DATE_TIMESTAMPS_AS_NANOSECONDS=false
and so on. For more information on how to configure the default object mapper without actually replacing it see https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-the-jackson-objectmapper and https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

How to auto configure `MetricsModule` using `JacksonAutoConfiguration` in Spring Boot

I'm trying to use MetricsModule to parse the response body of /metrics endpoint. For some reason the module never gets registered with the respective ObjectMapper.
I've found that this #PostConstruct method gets erased:
#PostConstruct
private void More ...registerModulesWithObjectMappers() {
Collection<Module> modules = getBeans(this.beanFactory, Module.class);
for (ObjectMapper objectMapper : getBeans(this.beanFactory, ObjectMapper.class)) {
objectMapper.registerModules(modules);
}
I've implemented my own code to do the same thing, but I'm not sure if this is the correct way to fix this issue.
Why wouldn't JacksonAutoConfiguration support this type of auto-configuration?
That method was removed in commit "Don't register Jackson Module beans with all ObjectMappers" for issue "Make Jackson auto-configuration more consistent and predictable". In short, it was done to not mess up with every ObjectMapper bean in the container.
To ensure that your Module bean gets registered with auto-configured ObjectMapper, make sure Module creation does not depend on an instance of ObjectMapper in any way. For example, do not autowire auto-configured ObjectMapper anywhere before Module bean is created.

Categories

Resources