REST xml answer - Jaxb - Amazon product API - java

I'm currently (trying) to work with the amazon product API to search thourgh items.
I have the response in XML, but i have an exception in jaxb, maybe i missed something..
Here is the XML :
XML response from Amazon
I want to extract items informations, but i'm getting some trouble.
Item class:
#XmlRootElement(name="ItemSearchResponse")
public class AmazonItem
{
private String name;
private String asin;
private String price;
public AmazonItem()
{
}
#XmlJavaTypeAdapter(CollapsedStringAdapter.class)
#XmlElement(name="Title")
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
#XmlJavaTypeAdapter(CollapsedStringAdapter.class)
#XmlElement(name="ASIN")
public String getAsin()
{
return asin;
}
public void setAsin(String asin)
{
this.asin = asin;
}
#XmlJavaTypeAdapter(CollapsedStringAdapter.class)
#XmlElement(name="FormattedPrice")
public String getPrice()
{
return price;
}
public void setPrice(String price)
{
this.price = price;
}
}
my builder :
public class AmazonItemBuilder
{
public AmazonItemBuilder()
{
}
public List<AmazonItem> build(InputStream response)
{
try
{
JAXBContext context = JAXBContext.newInstance(AmazonItem.class);
Unmarshaller unMarshaller = context.createUnmarshaller();
AmazonItem newItem = (AmazonItem) unMarshaller.unmarshal(response);
System.out.println(newItem);
}
catch (JAXBException e)
{
e.printStackTrace();
}
return null;
}
}
the "response" come from a URL response.openStream();
OK i forgot the error -_-
javax.xml.bind.UnmarshalException:
unexpected element (uri:"http://webservices.amazon.com/AWSECommerceService/2011-08-01", local:"ItemSearchResponse"). Expected elements are <{}ItemSearchResponse>
Thank you !

It appears that the XML document is namespace qualified. You can use the package level #XmlSchema location annotation to specify the namespace qualification for the entire document. Package level annotations go on a special call called package-info that looks like the following:
package-info
#XmlSchema(
namespace = "http://webservices.amazon.com/AWSECommerceService/2011-08-01",
elementFormDefault = XmlNsForm.QUALIFIED)
package com.your.pkg;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
For More Information
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html

I hope the top level class to covert the response to object should be "ItemSearchResponse", try creating a class with member variable "Items" which inturn will have another member object array "AmazonItem"

Related

No primary or default constructor found for interface java.util.List Rest API Spring boot

I am passing a request body to a POST request on postman similar to this:
"name":"Mars",
"artifacts":[
{
"elements":[
{
"name":"carbon",
"amount":0.5,
"measurement":"g"
}
],
"typeName":"typeA"
},
{
"elements":[
{
"name":"hydrogen",
"amount":0.2,
"measurement":"g"
}
],
"typeName":"typeB"
}
]
The create method in the rest controller looks like this.
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars;
Planet and all its nested objects have a default constructor such as:
public Planet() {}
However, I am not able to create a new planet object because of lack of a default constructor. Please help!
EDIT:
Planet class
public class Planet {
#JsonProperty("name")
private String name;
#Field("artifacts")
private List<Artifact> artifacts;
public Planet() {}
public Planet(String name, List<Artifact> artifacts)
{
this.name = name;
this.artifacts = artifacts;
}
//setters and getters
}
Artifact class:
public class Artifact() {
#Field("elements")
private List<Element> elements;
#JsonProperty("typeName")
private String typeName;
public Artifact() {}
public Artifact(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
}
Element class:
public class Element() {
#JsonProperty("elementName")
private String name;
#JsonProperty("amount")
private double amount;
#JsonProperty("measurement")
private String measurement;
public Element() {}
public Element(String name, double amount, String measurement)
{
//assignments
}
}
I had that the same error when I forgot the #RequestBody before the parameter
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
I don't understand what is the issue you are facing, but i can see an error straight away so guessing that is the issue you are facing, i am going to give you a solution.
Create a class which matches your json data structure like this :
Class PlanetData {
private String name;
private List<Planet> artifacts;
public PlanetData(String name, List<Planet> artifacts){
name = name;
artifacts = artifacts;
}
// include rest of getters and setters here.
}
Then your controller should look like this. Basically you needed to put #RequestBody to all the parameters you want to recieve from request JSON. Earlier you only put #RequestBody to name parameter not artifact parameter and since Request Body can be consumed only once, so you need a wrapper class to recieve the complete request body using single #RequestBody annotation.
#RequestMapping("/create")
public String create(#RequestBody PlanetData data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars.toString();
}
Edit : Looking at the Planet class, it also needs some modification
public class Planet {
private String typeName; // key in json should match variable name for proper deserialization or you need to use some jackson annotation to map your json key to your variable name.
private List<Element> elements;
public Planet() {}
public Planet(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
//setters and getters. Remember to change your setters and getter from name to typeName.
}
Hope this solves your issue.
This answer too might help someone.
When you are using spring framework for your API development, you may accidently import a wrong library for RequestBody and RequestHeader annotations.
In my case, I accidently imported library,
io.swagger.v3.oas.annotations.parameters.RequestBody
This could arise the above issue.
Please ensure that, you are using the correct library which is
org.springframework.web.bind.annotation.RequestBody
I guess, it’s trying to call new List() which has no constructor. Try using ArrayList in your signatures.
If it works this way, you have found the error. Then rethink your concept of calling methods, since you would usually want to avoid using implementations of List in method signatures
Make sure your request type is not of type GET
If so it is better not to send data as request body.
you should write as below:
...
public String create(#RequestBody JSONObject requestParams) {
String name=requestParams.getString("name");
List<Planet> planetArtifacts=requestParams.getJSONArray("artifacts").toJavaList(Planet.Class);
...

using one POJO class for different xml responses

When I call rest service I get different xml responses, with different xml root element. I would like to know, are there any opportunities to unmarshal these xmls to one pojo class.
For example, I have a class RecordingCreated.
#XmlRootElement(name = "recordingCreated")
public class RecordingCreated {
private String nodeID;
private String cameraID;
private String recPath;
private String recordingStatus;
public String getNodeID() {
return nodeID;
}
#XmlElement
public void setNodeID(String nodeID) {
this.nodeID = nodeID;
}
public String getCameraID() {
return cameraID;
}
#XmlElement
public void setCameraID(String cameraID) {
this.cameraID = cameraID;
}
public String getRecPath() {
return recPath;
}
#XmlElement
public void setRecPath(String recPath) {
this.recPath = recPath;
}
public String getRecordingStatus() {
return recordingStatus;
}
#XmlElement
public void setRecordingStatus(String recordingStatus) {
this.recordingStatus = recordingStatus;
}
}
After calling rest service I can get xml response in the form of
<recordingCreated>
<nodeID>"111</nodeID>
<cameraID>222</cameraID>\
<recordingID>333</recordingID>\
<recPath>rec</recPath>
<recordingStatus>recorded</recordingStatus>
</recordingCreated>
And in the form of
<error>
<code>444</code>
<description>broker: access denied</description>
</error>
When I got first xml resposne, JAXB unmarshal good
JAXBContext jaxbContext = JAXBContext.newInstance(RecordingCreated.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
RecordingCreated recordingCreated = (RecordingCreated) jaxbUnmarshaller.unmarshal(inputStream);
But when I got second response, of course, I got an error, like this
javax.xml.bind.UnmarshalException: unexpected element (uri:"",
local:"error"). Expected elements are <{}recordingCreated>]]
Question: Is there any opportunity having one class unmarshal two various xml responses with different root elements?
Try creating two different sub classes for your two different responses with their corresponding roots.
You can have the current class as posted as the parent for both of them and depending on the response you would get, call the required class.

Jackson with Builder Pattern

I've seen many questions around using jackson to serialize/deserialize java objects using builder patter, however, I can't figure out why this code below won't work. I'm using Jackson version 2.5.4
#JsonDeserialize(builder = User.Builder.class)
public class User {
private String name;
private User(Builder builder) {
this.name=builder.name;
}
#JsonPOJOBuilder(buildMethodName = "build")
public static class Builder {
private String name;
public Builder name(String name) {
this.name = name;
return this;
}
public User build() {
return new Learner(this);
}
}
}
Trying to output the string representation always prints an empty list {}
By default the #JsonPOJOBuilderexpects the builder methods to starts with with prefix.
You should override this in the annotation: #JsonPOJOBuilder(withPrefix = "")
You should also mark the name field with the #JsonProperty annotation, or add a getter, or use the JacksonFeatureAutoDetect feature; otherwise Jackson does not see name as a JSON property.

web service returns null insead of List<MyCustomObj> in Java

I use Netbeans 7.4 for my Java development. I try to use web service client/server application.
I am hiving a strange problem, let me describe it in details.
At server side, suppose i have DeviceInfo and LocationInfo classes like the following:
#XmlRootElement(name="DeviceInfo")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "DeviceInfo")
public class DeviceInfo {
#XmlElement(name = "Name")
private String name;
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
}
LocationInfo class has a member of DeviceInfo type:
#XmlRootElement(name="LocationInfo")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "LocationInfo")
public class LocationInfo {
#XmlElement(name = "Name")
private String name;
#XmlElement(name = "DeviceInfo", type=DeviceInfo.class)
private DeviceInfo deviceInfo;
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
public DeviceInfo getDeviceInfo() {
return deviceInfo;
}
public void setDeviceInfo(DeviceInfo deviceInfo) {
this.deviceInfo = deviceInfo;
}
}
And my web service class is like:
#WebService
public class DbAccess {
public List<LocationInfo> getListLocation()
{
List<LocationInfo> listLocation = new ArrayList<>();
LocationInfo location1 = new LocationInfo();
location1.setName("Location 1");
DeviceInfo device1_1 = new DeviceInfo();
device1_1.setName("Device1_1");
location1.setDeviceInfo(device1_1);
listLocation.add(location1);
LocationInfo location2 = new LocationInfo();
location2.setName("Location 2");
DeviceInfo device2_1 = new DeviceInfo();
device2_1.setName("Device2_1");
location2.setDeviceInfo(device2_1);
listLocation.add(location2);
return listLocation;
}
}
The web service simply returns list of LocationInfo.
At web client side, I simply added a web service client from Netbeans(New/Web Service Client). I just gave the web service WSDL address to IDE and it imported the web service types. So i can consume the web service at client side. The problem is that my web client gets null deviceInfo from getDeviceInfo() function of LocationInfo.
The code that i use in web client:
DbAccessService srv = new DbAccessService();
DbAccess db = srv.getDbAccessPort();
List<LocationInfo> list = db.getListLocation();
for(int i=0; i<list.size(); i++)
{
String str = "-" + list.get(i).getName();
if(list.get(i).getDeviceInfo() == null)
str += "\r\n---NULL";
else
str += "\r\n---" + list.get(i).getDeviceInfo().getName();
System.out.println(str);
}
This produces the following output:
-Location 1
---NULL
-Location 2
---NULL
Why do i get null insead of actual DeviceInfo?
edit* let me post as an answer for better formatting
No <root> was never used in your case, I just put that for an example. In your instance, since LocationInfo holds a DevinceInfo you want LocationInfo to be the root element. Defining DeviceInfo as a root element also confused the marshaller. Your Xml will look like
<LocationInfo>
<name>
Location 1
</name>
<DeviceInfo>
<name>
Device_1
</name>
</DeviceInfo>
</LocationInfo>
see how LocationInfo is the root element, defined by #XmlRootElement

JAXB null instead empty string during marshaling

How I can print 'null' as field value, when marshalling the string?
Example: error and error_code are Strings, and i want to use 'null' as a value indicating that there is no value/errors happened on the server side.
{
"error_code": null,
"error": null
}
Today, I have to use EMPTY values, so that "error_code" or "error" these fields generally fall into json, and if they were not explicitly initialized as this.errorCode = StringUtils.EMPTY;
So today, I have next json:
{
"error_code": "",
"error": ""
}
This is how that looks in a code:
#XmlRootElement()
#XmlAccessorType(XmlAccessType.FIELD)
public class Response
{
#SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(Response.class);
public static final String ERROR_FIELD_NAME = "error";
public static final String ERROR_CODE_FIELD_NAME = "error_code";
// #XmlJavaTypeAdapter(CafsResponse.EmptyStringAdapter.class)
#XmlElement(name = Response.ERROR_CODE_FIELD_NAME)
private String errorCode;
// #XmlJavaTypeAdapter(CafsResponse.EmptyStringAdapter.class)
#XmlElement(name = Response.ERROR_FIELD_NAME)
private String errorMessage;
// Empty Constructor
public Response()
{
this.errorCode = StringUtils.EMPTY; // explicit initialization, otherwise error_code will not appear as part of json, how to fix this this ?
this.errorMessage = StringUtils.EMPTY;
}
etc...
// Empty Constructor
public Response()
{
this.errorCode = null; // this variant dosn't work either, and error_code again didn't get to json
this.errorMessage = null;
}
See, #XmlJavaTypeAdapter, i thought that this potentially could help me - but no :)
Instead of null value, i'm getting "null" as string.
if (StringUtils.isEmpty(str))
{
return null;
}
return str;
{
"error_code": "null", // this is not whta i wanted to get.
"error": "null"
}
Any help on this? - ask me if something is not clear.
full list:
/**
* Empty string Adapter specifying how we want to represent empty strings
* (if string is empty - treat it as null during marhsaling)
*
*/
#SuppressWarnings("unused")
private static class EmptyStringAdapter extends XmlAdapter<String, String>
{
#Override
public String unmarshal(String str) throws Exception
{
return str;
}
#Override
public String marshal(String str) throws Exception
{
if (StringUtils.isEmpty(str))
{
return null;
}
return str;
}
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
You could use MOXy as your JSON provider to support this use case. Below is an example:
Response
MOXy will marshal properties marked with #XmlElement(nillable=true) to the representation you are looking for
(see: http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html).
package forum11319741;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
public static final String ERROR_FIELD_NAME = "error";
public static final String ERROR_CODE_FIELD_NAME = "error_code";
#XmlElement(name = Response.ERROR_CODE_FIELD_NAME, nillable = true)
private String errorCode;
#XmlElement(name = Response.ERROR_FIELD_NAME, nillable = true)
private String errorMessage;
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum11319741;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.setProperty("eclipselink.json.include-root", false);
Response response = new Response();
marshaller.marshal(response, System.out);
}
}
Output
{
"error_code" : null,
"error" : null
}
MOXy and JAX-RS
You can use the MOXyJsonProvider class to enable MOXy as your JSON provider in your JAX-RS application (see: http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html).
package org.example;
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class CustomerApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(MOXyJsonProvider.class);
set.add(CustomerService.class);
return set;
}
}
For More Information
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html

Categories

Resources