Problem Deserializing with Jackson using JAXB annotations in Spring MVC - java

I'm having trouble getting Jackson to correctly deserialize json into an object when calling a service (specifically we're using Jackson's ability to use JAXB annotations since we also want the service to use XML). I'm using Spring MVC and I'm using the RestTemplate class to make calls to the service.
Here is where I setup the MappingJacksonHttpMessageConverter for my junit:
ObjectMapper jsonMapper = new ObjectMapper();
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
jsonMapper.getDeserializationConfig().setAnnotationIntrospector(introspector);
jsonMapper.getSerializationConfig().setAnnotationIntrospector(introspector);
jsonMapper.getSerializationConfig().setSerializationInclusion(Inclusion.NON_NULL);
MappingJacksonHttpMessageConverter jacksonConverter = new MappingJacksonHttpMessageConverter();
jacksonConverter.setObjectMapper(jsonMapper);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(jacksonConverter);
template.setMessageConverters(converters);
And I call the service like so:
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Accept", "application/json");
HttpEntity<String> requestEntity = new HttpEntity<String>(requestHeaders);
ResponseEntity<NamedSystem> responseEntity = template.exchange(baseURL + "/{NamedSystemId}",
HttpMethod.GET, requestEntity, NamedSystem.class, orgId1);
My NamedSystem class is set up like so:
#XmlRootElement(name = "NamedSystem", namespace = "http://schemas.abc.workplace.com/NamedSystem")
public class NamedSystem {
private String id;
private String name;
private String description;
private Set<NamedSystemAlias> aliases;
private String href;
#XmlAttribute(required = false, name = "id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#XmlAttribute(required = false, name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlAttribute(required = false, name = "description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#XmlElementWrapper(required = false, name = "aliases", namespace = "http://schemas.abc.workplace.com/NamedSystem")
#XmlElement(required = false, name = "alias", namespace = "http://schemas.abc.workplace.com/NamedSystem")
public Set<NamedSystemAlias> getAliases() {
return aliases;
}
public void setAliases(Set<NamedSystemAlias> aliases) {
this.aliases = aliases;
}
#XmlAttribute(required = true, name = "href")
public String getHref() {
return href;
}
public void setHref(String href) {
this.href = href;
}
}
This is the error that results:
org.springframework.web.client.ResourceAccessException: I/O error: Unrecognized field "NamedSystem" (Class com.workplace.abc.named.NamedSystem), not marked as ignorable
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#c1429c; line: 1, column: 2]; nested exception is org.codehaus.jackson.map.JsonMappingException: Unrecognized field "NamedSystem" (Class com.workplace.abc.named.NamedSystem), not marked as ignorable
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#c1429c; line: 1, column: 2]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
....
Caused by: org.codehaus.jackson.map.JsonMappingException: Unrecognized field "NamedSystem" (Class com.workplace.abc.named.NamedSystem), not marked as ignorable
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#c1429c; line: 1, column: 2]
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:159)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:247)
at org.codehaus.jackson.map.deser.StdDeserializer.reportUnknownProperty(StdDeserializer.java:366)
at org.codehaus.jackson.map.deser.StdDeserializer.handleUnknownProperty(StdDeserializer.java:352)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:543)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:402)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:287)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:1588)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1172)
at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readInternal(MappingJacksonHttpMessageConverter.java:132)
at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:154)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:74)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:619)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:446)
... 32 more
It seems it doesn't recognize the rootElement 'NamedSystem' to be able to deserialize. How would I get it to do that? I've seen examples that use the same JAXB annotations and they work fine so I'm not sure what's different about my case or how I might force it to correctly deserialize it. If anyone can offer any help, I'd appreciate it.

If anyone comes along this kind of problem, this might fix it for you: Enable Jackson to not output the class name when serializing (using Spring MVC)
See my answer and follow the link for an example.

Related

passing another object into current rest service does not throw an exception

this my is rest request that is compatible for another service :
{
"fromDate": 1562773101000,
"toDate": 1563118701000,
"turnOverType": 4,
"fromAmount": 1,
"toAmount": 10000000,
"voucherDescription": null,
"articleDescription": null,
"referenceNumbers": [],
"offset": 3,
"pageSize": 20,
"iban": "BLAHBLAHBLAHBLAH"
}
and this is corresponding model that not match request :
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "TransferRequestInquiryFilter")
public class TransferRequestInquiryFilter implements Serializable {
#XmlElement(name = "sourceIbans")
private List<String> sourceIbans;
#XmlElement(name = "transferType")
private TransferType transferType;
#XmlElement(name = "fromTransferDate")
private Timestamp fromTransferDate;
#XmlElement(name = "toTransferDate")
private Timestamp toTransferDate;
#XmlElement(name = "fromRegistrationDate")
private Timestamp fromRegistrationDate;
#XmlElement(name = "toRegistrationDate")
private Timestamp toRegistrationDate;
#XmlElement(name = "trackingNumbers")
private List<String> trackingNumbers;
#XmlElement(name = "referenceNumbers")
private List<String> referenceNumbers;
#XmlElement(name = "transactionIds")
private List<String> transactionIds;
#XmlElement(name = "status")
private TransactionStatus status;
#XmlElement(name = "fromAmount")
private Long fromAmount;
#XmlElement(name = "toAmount")
private Long toAmount;
#XmlElement(name = "destinationIbans")
private List<String> destinationIbans;
and this is my controller ..
#RequestMapping(value = "/inquiry", method = RequestMethod.POST)
public #ResponseBody
ResponseEntity<List<ExtendedTransferRequest>> transferInquiry(#RequestBody #Valid TransferRequestInquiryFilter transferRequestInquiryFilter
, BindingResult bindingResult) {
// when validation not works return bad request
List<ErrorObject> errorObjects = requestInquiryValidator.validate(transferRequestInquiryFilter);
if (errorObjects.size() > 0) {
// just throw bad request and not detail of them
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
List<ExtendedTransferRequest> extendedTransferRequestList = new ArrayList<>();
ExtendedTransferRequest extendedTransferRequest = new ExtendedTransferRequest();
List<SettlementTransaction> settlementTransactionList = settlementSearch.findSettlement(transferRequestInquiryFilter);
extendedTransferRequestList = TransferInquiryResponseMapper.INSTANCE.SettlementTransactionInquiryResponse(setlementTransactionList);
return new ResponseEntity<>(extendedTransferRequestList, HttpStatus.OK);
}
just fromAmount and toAmount fills. but i want to get an exception for this situation and throw a bad request for client. how can i do that? If I get name conflict or type conflict between rest request and model , I need to handle it and riase a bad request for client. i am using spring mvc 5 and jackson-core and jackson-databind 2.9.4
Using validation-api, annotate proper validation for fields and #validated before controller method and using #valid before RequestBody object would throw proper validation exception.

Serialize XML element having property named value using Jackson

I'm trying to deserialize/serialize xml content with below element.
<?xml version="1.0" encoding="utf-8" ?>
<confirmationConditions>
<condition type="NM-GD" value="something">no modification of guest details</condition>
</confirmationConditions>
How can I properly create java beans with jackson annotations to parse this correctly. I've tried with JAXB annotations and jackson fails saying it can't be having to value fields. With below java beans I got following error.
public class Condition
{
#JacksonXmlProperty( isAttribute = true, localName = "type" )
private String type;
#JacksonXmlProperty( isAttribute = true, localName = "value" )
private String value;
private String text;
}
Error
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "" (class Condition), not marked as ignorable (3 known properties: "value", "type", "text"])
at [Source: (File); line: 3, column: 73] (through reference chain: ConfirmationConditions["condition"]->Condition[""])
Basically what I want is to map element content to text field. I have no control over xml so changing it would not work for me.
The thing you need here is to add #JacksonXmlText
class Condition {
#JacksonXmlProperty(isAttribute = true)
private String type;
#JacksonXmlProperty(isAttribute = true)
private String value;
#JacksonXmlText
private String text;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
And parse it this way:
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
XmlMapper xmlMapper = new XmlMapper(module);
xmlMapper.readValue(
"<condition type=\"NM-GD\" value=\"something\">no modification of guest details</condition>", Condition.class);

#RequestBody Object does not deserialize automatically

I use Spring-boot 2.0.1 with WebFlux as Rest server.
In me RestController I would like to automatically deserialize an object (Product). But I get a Jackson error as if ParameterNamesModule was not registered.
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.truc.Product (no Creators, like default
construct, exist): cannot deserialize from Object value (no delegate-
or property-based Creator) at [Source: UNKNOWN; line: -1, column: -1]
at
com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at
com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at
com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1290)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
at
com.fasterxml.jackson.databind.ObjectReader._bind(ObjectReader.java:1574)
at
com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:965)
at
org.springframework.http.codec.json.AbstractJackson2Decoder.lambda$decodeInternal$0(AbstractJackson2Decoder.java:113)
... 287 common frames omitted
I have jackson-module-parameter-names in my pom
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
And this the route in RestController
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST)
public Mono<Entity<Product>> postProduct(#RequestBody Product product) {
return productService.insert(product);
}
If I try to deserialize manually it works :
#Autowired
private ObjectMapper objectMapper;
...
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST)
public Mono<Entity<Product>> postProduct(ServerWebExchange exchange) {
return exchange.getRequest().getBody()
.last().flatMap(buffer -> {
try {
Product product = objectMapper.readValue(buffer.asInputStream(), Product.class);
return productService.insert(product);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
This is my Product.class
package com.truc;
import java.time.LocalDate; import java.util.Optional;
public final class Product {
public static final String PREFIX = "PT";
public final String description;
public final String shortDescription;
public final String sku;
public final float weight;
public final LocalDate newSince;
public final LocalDate newUntil;
public final Status status;
public final Visibility visibility;
public final String metaKeywords;
public Product(String description, String shortDescription, String sku, Float weight,
LocalDate newSince, LocalDate newUntil, Status status, Visibility visibility, String metaKeywords) {
this.description = description;
this.shortDescription = shortDescription;
this.sku = sku;
this.weight = Optional.ofNullable(weight).orElse(0f);
this.newSince = newSince;
this.newUntil = newUntil;
this.status = status;
this.visibility = visibility;
this.metaKeywords = metaKeywords;
}
public enum Status {
ACTIVE, INACTIVE
}
public enum Visibility {
FULL, CATALOG, SEARCH, NONE
}
}
If I understand #JsonCreator is not required after jackson-databind 2.9, I use jackson 2.9.5
If I add #JsonCreator I get a new error 415 Unsupported Content-Type 'application/json' ...
I don't understand where I'm wrong ?
Thanks
You have to add default constructor in your RequestBody class which is ProductClass then it will work
As explain here #EnableWebFlux deactivate all the webflux auto-configuration. And I use it in Configuration class without extend the class with WebFluxConfigurer.
When I remove #EnableWebFlux it work again, the ObjectMapper is configured as expected.
So it's ok
Thanks

Jackson ignores JsonProperty annotation in deserialization

I've got a bit of a conundrum. I'm trying to deserialize a json message into a pojo using the builder pattern and I'm getting the following error:
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Information"
(class com.verification.client.models.response.Response$Builder), not marked as ignorable (3 known properties: "status", "products", "information"])
This is very confusing to me as I've clearly marked the field in the pojo with a JsonProperty annotation:
#JsonDeserialize(builder = Response.Builder.class)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Response {
#JsonProperty("Status")
private final Optional<Status> status;
#JsonProperty("Products")
private final Optional<List<ResponseProduct>> products;
#JsonProperty("Information") //Here's where the field is defined
private final Optional<List<ResponseInformation>> information;
private Response(final Builder b){
this.status = b.status;
this.products = b.products;
this.information = b.information;
}
public Optional<Status> getStatus() {
return status;
}
public Optional<List<ResponseProduct>> getProducts() {
return products;
}
public Optional<List<ResponseInformation>> getInformation() {
return information;
}
#JsonPOJOBuilder(buildMethodName = "build", withPrefix = "")
public static class Builder{
private Optional<Status> status;
private Optional<List<ResponseProduct>> products = Optional.empty();
private Optional<List<ResponseInformation>> information = Optional.empty();
public Builder(){}
public Builder status(final Status status){
this.status = Optional.of(status);
return this;
}
public Builder products(final List<ResponseProduct> products){
this.products = Optional.of(products);
return this;
}
public Builder information(final List<ResponseInformation> information){
this.information = Optional.of(information);
return this;
}
public Response build(){
return new Response(this);
}
}
}
I have a feeling it's something small, but at this point I am at a loss for why my code is behaving this way.
P.S.
Here's the json I'm deserializing
{
"Information": [{
"InformationType": "error-details",
"Code": "internal_application_error",
"Description": "Error: Internal",
"DetailDescription": []
}]
}
Solved this a while back, answering for posterity.
The issue I was having is that the build methods were not being correctly interpreted, but by defining #JsonSetter annotations on the methods of the static build class Jackson was able to correctly interpret the input and build the object.

List of objects in Spring MVC #RequestBody produces "Syntactically incorrect request"

I'm trying to update an object via REST services using Spring MVC + Swagger Annotations.
The method is something like this:
#ApiOperation(value = "Modifies the entity")
#RequestMapping(value = "/entity", method = RequestMethod.PUT, headers = "Accept=application/json")
#APIMonitor
#ResponseBody
public PubTagger saveEntityDetails(
HttpServletResponse response,
ModelMap model,
#RequestBody final EntityClass entityInfo
)
throws Exception {
...
}
The entity definition is:
{
"id": "long",
"description": "string",
"name": "string",
"properties": [
{
"name": "string",
"value": "string"
}
]
}
It gives me an error
The request sent by the client was syntactically incorrect ()
But it only happens when I fill the objects inside the Properties field. If I leave it empty it succeeds. So I deduce there's something wrong in Spring MVC with nested objects inside lists.
Is there anything I'm missing here? Do I have to specify anything in the model to make it work?
Edit: Posting Entity class
public class Entity {
private Long id;
private String name;
private String description;
private List<Property> properties = new ArrayList<>();
public void setId(final Long id) {
this.id = id;
}
public Entity() {
super();
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
public List<Property> getProperties() {
return properties;
}
public void setProperties(List<Property> properties) {
this.properties = properties;
}
}
Thanks, I found the error.
It was the class Property that only had the Parametrized constructor, without default constructor which made unable to marshall the JSON requestBody into an object.

Categories