How do I use JAXB annotations with Spring RestTemplate? - java

I'm trying to automatically deserialize XML formatted response using Spring's RestTemplate. I'm using Jackson's jackson-dataformat-xml module, for which Spring Boot is set to auto-configure. I want to use JAXB annotations in the class I want to deserialize to, but it won't seem to work. Here's a sample of what I want the class to look like:
#XmlRootElement(name="Book")
public class Book {
#XmlElement(name="Title")
private String title;
#XmlElement(name="Author")
private String author;
}
This is based on the following XML sample:
<Book>
<Title>My Book</Title>
<Author>Me</Author>
</Book>
However, with class annotated like that above, the fields are always set null. I did some experiments and found out that the deserialization works if I use Jackson's #JsonProperty to annotate the child elements:
#XmlRootElement(name="Book")
public class Book {
#JsonProperty("Title")
private String title;
#JsonProperty("Author")
private String author;
}
It works, but somehow I feel like it's kind of awkward. Is there a way to get the JAXB annotations work like in my first example?
Jackson provides jackson-module-jaxb-annotations module for the XML databinding to work with JAXB annotations. However, I'm not sure how to setup the ObjectMapper being used by RestTemplate to use this module.

To address this issue, I needed to register an instance of JaxbAnnotationModule to every ObjectMapper used by converters added to Spring's RestTemplate. The class is included in Jackson's jackson-module-jaxb-annotations module, which I added to my build through Gradle.
With the dependency added to my project, what I did next was to configure the RestTemplate used by my application. The ObjectMapper instances are being used by MappingJackson2XmlHttpMessageConverters configured automatically by Spring. I had to register JaxbAnnotationModule instance to each ObjectMapper used in every converter, so the first task was to find all MappingJackson2XmlHttpMessageConverters using:
//create module
JaxbAnnotationModule jaxbAnnotationModule = new JaxbAnnotationModule();
restTemplate.getMessageConverters().stream().filter(converter -> {
return converter instanceof MappingJackson2XmlHttpMessageConverter;
})
Once I had all the relevant converters, I then registered the module to each of their ObjectMappers:
forEach(converter -> {
((MappingJackson2XmlHttpMessageConverter) converter)
.getObjectMapper()
.register(jaxbAnnotationModule);
});

I suspect that in second case Spring simply ignores root level JAXB annotation, because by default Jackson will resolve name of the class properly.
In order to use JAXB annotations you have to use library jackson-xc
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
</dependency>
These artilces may be also useful:
1) http://wiki.fasterxml.com/JacksonJAXBAnnotations
2) http://springinpractice.com/2011/12/06/jackson-json-jaxb2-xml-spring
3) How can we configure the internal Jackson mapper when using RestTemplate?

If solution by #Psycho Punch still not working for you, This is another alternative:
add com.fasterxml.jackson.dataformat:jackson-dataformat-xml dependency.
use XmlMapper instead of ObjectMapper to your MappingJackson2XmlHttpMessageConverter. For example:
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(new JaxbAnnotationModule());
forEach(converter -> {
((MappingJackson2XmlHttpMessageConverter) converter).setObjectMapper(xmlMapper)
});
HTH

If someone still have to deal with this kind of issue, Springboot provide an elegant solution :
Any beans of type com.fasterxml.jackson.databind.Module are automatically registered with the auto-configured Jackson2ObjectMapperBuilder and are applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.
So adding this in one of your configuration class should be enough:
#Bean
public Module jaxbModule() {
return new JaxbAnnotationModule();
}
Source: https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-customize-the-jackson-objectmapper

Related

Ignore JSON attribute cases declared with #JsonProperty with Spring RestTemplate

I use #JsonProperty to serialize data from a JSON through Spring RestTemplate's exchange.
#JsonProperty("ip_address")
public String ipAddress;
#JsonProperty("port")
public Integer port;
I need this property to recognize both upper and lowercase versions of the attribute names i.e. "ip_address" and "IP_ADDRESS" set in #JsonProperty should be recognized.
I have tried the following and none worked:
#JsonFormat(with=JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) applied on a class level does not work with existing issue reported in GitHub. Also does not work when applied to each attribute in the model class.
use MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES on an ObjectMapper bean.
This example using ObjectMapper and RestTemplate also didn't work even with a combination code with the example on the item before.
All three just have null values for their respective attributes because I disabled error on unknown attributes (a.k.a. the different letter case) for the template.
You can tell jackson to convert all your property names into e.g. SNAKE_CASE variants and set your #JsonProperty accordingly:
Example:
in spring boot set the property in application.properties
spring.jackson.property-naming-strategy=SNAKE_CASE
or you can enable it for just your single class and annotate the class with:
#JsonNaming(PropertyNamingStrategy.SNAKE_CASE.class)
and then set #JsonProperty:
#JsonProperty(vale="ip_address")
I was able to get this working without changing my original pojo class #JsonProperty configs. Using the Object Mapper and Rest Template example you linked to but instead of a Property Naming strategy use the case insensitive mapper feature
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
For Spring Boot App to Ignore Case for JSON attributes names:
Step 1: Make sure your POJO/Domain/Model class has a constructor with respective parameters, and remove any zero arg constructor.
Step 2: Add Jackson lib as a dependency
Ex:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.6.5</version>
</dependency>
Step 3: Enable in application.properties file as below
spring.jackson.mapper.accept_case_insensitive_properties=true

Supply Polymorphic De-serialization/Serialization parameters to REST endpoint without changing POJOs

I am working on a JAX-RS application, where endpoints Consume and Produce JSON type data. And I am using Jackson for that purpose.
As I know one of the ways to handle Polymorphic behavior in Jackson is to use #JsonTypeInfo and #JsonSubTypes on POJOs.
But in my case POJOs are not part of code, they're embedded in a JAR file and I can't edit them.
Is there any way where I can specify this info to Jackson externally without POJO annotations ?
Please point me to the right direction of this has already been discussed earlier.
Is there any way where I can specify this info to Jackson externally without POJO annotations?
Using annotations is still a valid approach if you consider mix-in annotations.
When modifying the source code is not an option, you can use mix-in annotations to add Jackson annotations to a bean. You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment statically defined ones.
First define a mix-in annotation interface or class:
#JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "class")
public interface FooMixIn {
}
Then configure ObjectMapper to use the defined interface as a mix-in for your POJO:
ObjectMapper mapper = new ObjectMapper().addMixIn(Foo.class, FooMixIn.class);
All annotation sets that Jackson recognizes can be mixed in. For more details, have a look at the Jackson documentation.

Jackson parse json with unwraping root, but without ability to set #JsonRootName

The rest service responds with
<transaction><trxNumber>1243654</trxNumber><type>INVOICE</type></transaction>
or in JSON:
{"transaction":{"trxNumber":1243654,"type":"INVOICE"}}
There is no problems when I use:
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
And as resulting class
#JsonRootName("transaction")
public class Transaction {
private String trxNumber;
private String type;
//getters and setters
}
But actually I should use the Transaction class from 3rd party jar, which is exact like above, but has no #JsonRootName("transaction") annotation.
So I end up with
Could not read JSON: Root name 'transaction' does not match expected ('Transaction') for type...
Is there any ways to force Jackson parse to Transaction class without adding any stuff to the Transaction class itself (as I get this file as part of a binary jar)?
I've tried custom PropertyNamingStrategy, but it seems has to do only with field and getter/setter names, but not class names.
Java7, Jackson 2.0.5.
Any suggestions? thanks.
You can do it with mixin feature. You can create simple interface/abstract class like this:
#JsonRootName("transaction")
interface TransactionMixIn {
}
Now, you have to configure ObjectMapper object:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
mapper.addMixInAnnotations(Transaction.class, TransactionMixIn.class);
And finally you can use it to deserialize JSON:
mapper.readValue(json, Transaction.class);
Second option - you can write custom deserializer for Transaction class.

Serialization and Deserialization with Jackson: how to programmatically ignore fields?

I'm using Jackson to serialize and deserialize objects. The problem is that sometimes I want to show a field and sometimes not.
Actually I'm using the #JsonIgnore to avoid the printing of the property when I don't need it. When I need it I'm disabling the Property through
mapper.getSerializationConfig().disable(SerializationConfig.Feature.USE_ANNOTATIONS);
but this will disable also other annotations that I need.
How can I get the result that I need? Using Views? Any example?
A little pojo to understand what I want:
class User {
private String username;
#JsonIgnore
private String password;
// getter setter
}
writeToDB() {
mapper.getSerializationConfig().disable(SerializationConfig.Feature.USE_ANNOTATIONS);
mapper.writeValueAsString(user);
}
and through the REST API you can get the username without the password (thanks to the JsonIgnore)
In the end I've handled this in a different way. I was using Jersey and Guice so was a little hard to find out how, but I did it.
Basically I used the MixIn annotations of Jackson but using Jersey I had to create the ObjectMapper into a Provider and then bind it with Guice.
In this way when I'm using the REST service Jersey will use the ObjectMapper defined in the Provider; when storing the stuff Jackson will use the standard ObjectMapper.
Now some code.
create the PrivateUser class:
public abstract class PrivateUser {
#JsonIgnore abstract String getPassword();
}
create the provider:
#Provider
public class JacksonMixInProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> aClass) {
final ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(User.class, PrivateUser.class);
return mapper;
}
}
and bind it:
bind(JacksonMixInProvider.class).asEagerSingleton();
That's it! :D
Hope this will help someone else to waste less time!
I think you should handle this a different way, by creating a custom Jackson serializer that can selectively serialize/ignore the password.
Annotations like this should be considered immutable at runtime. There may be some reflection trick to extract the JsonIgnore and set the value, but, if so, this would be really heavy-handed.

JsonTypeInfo does not serialize when using Jackson with Jersey

I annotated a JAXB class with JsonTypeInfo so that I could serialize polymorphic classes easily. However, the annotation does not show up when serialized by Jersey. To be more specific, it shows up when using ObjectMapper but not as a return type from a resource. I am very confused right now as this seems to be a problem with Jersey => Jackson interaction.
To debug things, I used the jsonfromjaxb example from the jersey-samples to localize my problem. I added the following to the Flights class to have it serialize out to #class.
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
I have the following methods available in the resource, one which just returns the JAXB object and one which manually uses ObjectMapper
#GET
#Produces({"application/json"})
public synchronized Flights getFlightList() {
return myFlights;
}
#GET
#Path("/object_mapper")
#Produces({"application/json"})
public synchronized String getFlights() throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(myFlights);
}
The result of querying /jsonfromjaxb/flights
{"flight":[{"flightId":"OK123","company":"Czech Airlines","number":123,"aircraft":"B737"},{"flightId":"OK124","company":"Czech Airlines","number":124,"aircraft":"AB115"}]}
The result of querying /jsonfromjaxb/flights/object_mapper
{"#class":"com.sun.jersey.samples.jsonfromjaxb.jaxb.Flights","flight":[{"number":123,"company":"Czech Airlines","aircraft":"B737","flightId":"OK123"},{"number":124,"company":"Czech Airlines","aircraft":"AB115","flightId":"OK124"}]}
Thanks,
Ransom
I think it looks like you aren't using Jackson-based serialization (that is, one that uses ObjectMapper; low-level jackson generator is used for most JSON output, including ones where binding is done differently). If you were, it definitely should look like what you see from explicit use. So it seems to be matter of changing Jersey JSON configuration.

Categories

Resources