I'm trying to force brackets on lists that contain only one element.
I want something like this:
{"id":"0","industries":[{"id":"0","name":"Technologies"}],"name":"Google Inc."}
But I get:
{"id":"0","industries":{"id":"0","name":"Technologies"},"name":"Google Inc."}
Here is my Entity:
#Entity
#XmlRootElement
public class Company {
private int id;
private String name;
private String description;
#XMLElement(name="industries")
private List<Industry> industryList;
[...]
And finally, my JAXB Context Resolver:
public JAXBContextResolver() throws Exception {
MappedBuilder builder = JSONConfiguration.mapped();
builder.arrays("industries");
builder.rootUnwrapping(true);
this.context = new JSONJAXBContext(builder.build(), Company.class);
}
thanks for your help, but I found the answer. You actually need to specify a JAXBContextResolver which specify natural JSON configuration. You need to provide a type list of every container that need to be transform to JSON. In this example, you can see that I specified the GetCompanyResponse that is a container of Company.
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class[] types = { GetCompanyResponse.class };
public JAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (Class clazz : types) {
if (clazz.equals(objectType)) {
return context;
}
}
return null;
}
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
EclipseLink JAXB (MOXy) provides native JSON-binding support. It will correctly marshal collections of size 1 wrapped as a JSON array. Below is a complete example.
Company
package forum3946102;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Company {
private int id;
private String name;
private String description;
#XmlElement(name = "industries")
private List<Industry> industryList;
}
Industry
package forum3946102;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Industry {
private int id;
private String name;
}
jaxb.properties
In order to specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain classes with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum3946102;
import java.io.StringReader;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Company.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty("eclipselink.media-type", "application/json");
unmarshaller.setProperty("eclipselink.json.include-root", false);
String jsonString = "{\"id\":\"0\",\"industries\":[{\"id\":\"0\",\"name\":\"Technologies\"}],\"name\":\"Google Inc.\"}";
StreamSource jsonSource = new StreamSource(new StringReader(jsonString));
Company company = unmarshaller.unmarshal(jsonSource, Company.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.setProperty("eclipselink.json.include-root", false);
marshaller.marshal(company, System.out);
}
}
Output
Below is the output from running the demo code:
{"id" : 0, "name" : "Google Inc.", "industries" : [{"id" : 0, "name" : "Technologies"}]}
For More Information
JSON Binding with EclipseLink MOXy - Twitter Example
MOXy as Your JAX-RS JSON Provider - Server Side
I'm not too sure about this, but try removing the #XMLElement annotation for industryList
I have done stuff the other way around: using jaxb to generate java classes from xsd schema files. I've looked at the generated classes with collection fields, and they don't have any specific annotations on them.
Also you may wanna try JSON Lib: http://json-lib.sourceforge.net/
you could do things like:
jsonString = JSONObject.fromObject(pojoObject)
which will generate json string that will incorporate e.g. collections of complex types.
You could then send the jsonString using e.g. HttpServletResponse.
I would recommend serializing DTO objects rather than serialize your entity objects.
Drill down JSONObject and swap out an area that needs to be JSONArray. Once created, json putOpt will replace old reference object with new.
Before
"Investment": {"SubAccount": {
"id": "SubAccountId_2_CORP",
"AllocPercent": "100.0",
"ProductCode": "PC01",
"ProductFullName": "Product Full Name"
}}
After
"Investment": {"SubAccount": [{
"id": "SubAccountId_2_CORP",
"AllocPercent": "100.0",
"ProductCode": "PC01",
"ProductFullName": "Product Full Name"
}]}
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.XML;
private static int PRETTY_PRINT_INDENT_FACTOR = 4;
.
.
.
.
try {
org.json.JSONObject xmlJSONObj = XML.toJSONObject(inBatchTrans.getINPUT_MESSAGE());
try {
JSONArray subAcctArray = xmlJSONObj.getJSONObject("TXLife").getJSONObject("TXLifeRequest").getJSONObject("OLifE").getJSONObject("Holding").getJSONObject("Investment").getJSONArray("SubAccount");
// Already JsonArray do nothing
} catch (Exception e) {
// convert to Array
JSONObject subAcctObj = xmlJSONObj.getJSONObject("TXLife").getJSONObject("TXLifeRequest").getJSONObject("OLifE").getJSONObject("Holding").getJSONObject("Investment").getJSONObject("SubAccount");
JSONArray keys = subAcctObj.names();
JSONArray values = subAcctObj.toJSONArray(keys);
JSONObject subAccount = new JSONObject();
JSONArray subAccountList = new JSONArray();
int key = keys.length();
for (int i = 0; i < key; i++) {
subAccount.put(keys.get(i).toString(), values.get(i));
}
subAccountList.put(0, subAccount);
xmlJSONObj.getJSONObject("TXLife").getJSONObject("TXLifeRequest").getJSONObject("OLifE").getJSONObject("Holding").getJSONObject("Investment").putOpt("SubAccount", subAccountList);
}
String jsonPrettyPrintString = xmlJSONObj.toString(PRETTY_PRINT_INDENT_FACTOR);
System.out.println(jsonPrettyPrintString);
} catch (org.json.JSONException je) {
System.out.println(je.toString());
}
Related
I am trying to unmarshal the XML and map it to the Java POJO. My XML can have some of the user-defined elements which can be random so I would like to store them. After researching I found that I can use #XmlAnyElement(lax=true). I am trying to use the XMLAdapter along with the #XmlAnyElement but for some reason, the method unmarshal within my XMLAdapter is not being called at all due to which I am unable to map the user-defined fields.
Can someone please explain to me how to unmarshal the user-defined fields to my Map<String,Object> for the marshalling everything is working fine and I am using the approach mentioned here. But unmarshalling is not being called at all which is bit confusing for me.
Following is my XML which needs to be unmarshalled. (Please note that the namespace can be dynamic and user-defined which may change in every xml):
<Customer xmlns:google="https://google.com">
<name>Batman</name>
<google:main>
<google:sub>bye</google:sub>
</google:main>
</Customer>
Following is my Customer class to which XML needs to be unmarshalled;
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private String name;
#XmlAnyElement(lax = true)
#XmlJavaTypeAdapter(TestAdapter.class)
private Map<String, Object> others = new HashMap<>();
//Getter Setter and other constructors
}
Following is my XMLAdapter (TestAdapter) class which will be used for marshalling and unmarshalling the user-defined fields. The unmarshalling method is not being called at all. However the marshalling method works as expected based on the code provided here.
class TestAdapter extends XmlAdapter<Wrapper, Map<String,Object>> {
#Override
public Map<String,Object> unmarshal(Wrapper value) throws Exception {
//Method not being called at all the following SYSTEM.OUT is NOT PRINTED
System.out.println("INSIDE UNMARSHALLING METHOD TEST");
System.out.println(value.getElements());
return null;
}
#Override
public Wrapper marshal(Map<String,Object> v) throws Exception {
return null;
}
}
class Wrapper {
#XmlAnyElement
List elements;
}
I have used the package-info.java file and it has been filled with following contents:
#jakarta.xml.bind.annotation.XmlSchema(namespace = "http://google.com", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED)
package io.model.jaxb;
I researched a lot but could not find any answer which does something similar. Also, tried a lot of things but none worked. Hence, posting the same here and looking for some suggestion or workaround.
I have few doubts with regards to unmarshalling:
Why my XMLAdapter TestAdapter.class unmarshal method is not being called during the unmarshalling?
How can I unmarshal the XML fields which can appear randomly with namespaces?
Am I doing something wrong or is there something else I should do to read the namespaces and elements which appear dynamically?
*** FOLLOWING IS EDITED SECTION BASED ON RESPONSE FROM Thomas Fritsch ****
Based on the response I have edited my class but still not working as expected:
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private String name;
#JsonIgnore
#XmlJavaTypeAdapter(TestAdapter.class)
private List<Object> others;
#XmlTransient
#XmlAnyElement(lax = true)
private List<Element> another = new ArrayList<>();
}
So what's happening is that if I use #XmlTransient then the field another will not be populated during the unmarshalling and I need to keep it #XmlTransient because I do not want it during the marshalling similarly I have made #JsonIgnore for Map<String, Object> others because I do not need it during the unmarshalling but both things are conflicting with each other and not able to obtain the the required output.
My main goal is to convert the XML file to JSON and vice versa. For the above mentioned XML file I would like to obtain the following output in JSON:
{
"Customer": {
"name": "BATMAN",
"google:main": {
"google:sub": "bye"
}
}
}
Similarly when I convert this JSON then I should get the original XML.
Following is my Main.class:
class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException, JsonProcessingException {
//XML to JSON
JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("Customer.xml");
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
final XMLStreamReader streamReader = xmlInputFactory.createXMLStreamReader(inputStream);
final Customer customer = unmarshaller.unmarshal(streamReader, Customer.class).getValue();
final ObjectMapper objectMapper = new ObjectMapper();
final String jsonEvent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
System.out.println(jsonEvent);
//JSON to XML
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(customer, System.out);
}
}
The Map type of your others property is not suitable for #XmlAnyElement.
According to the its javadoc the #XmlAnyElement annotation
is meant to be used with a List or an array property
(typically with a List or array of org.w3c.dom.Element).
May be you have confused this with the #XmlAnyAttribute annotation
which indeed is used with a Map property.
Hence in your Customer class the others property without using an adapter would look like this:
name;
#XmlAnyElement(lax = true)
private List<Element> others = new ArrayList<>();
And the others property with using an adapter should look like this:
#XmlAnyElement(lax = true)
#XmlJavaTypeAdapter(TestAdapter.class)
private List<MyObject> others = new ArrayList<>();
When doing this way, then JAXB will actually call your adapter.
The adapter's job is to transform between Element and MyObject.
public class TestAdapter extends XmlAdapter<Element, MyObject> {
#Override
public MyObject unmarshal(Element v) throws Exception {
...
}
#Override
public Element marshal(MyObject v) throws Exception {
...
}
}
After trying out a lot of things, I was able to get it working. Posting the solution for the same so it can be helpful to someone in the future.
I have used Project Lombok so you can see some additional annotations such as #Getter, #Setter, etc
Method-1:
As mentioned #Thomas Fritsch you have to use the #XmlAnyElement(lax=true) with List<Element> I was using with the Map<String, Object>.
Method-2:
You can continue to use Map<String,Object> and use #XmlPath(".") with adapter to get it working. Posting the code for the same here: (I have added one additional field age other that everything remain the same)
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "age", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Customer {
#XmlElement(name = "name")
private String name;
private String age;
#XmlJavaTypeAdapter(TestAdapter.class)
#XmlPath(".")
private Map<String, Object> others;
}
Following is the TestAdapter.class posting the same for reference. When you unmarhsal the method unmarshal in TestAdapter will get called and you can do anything you need.
class TestAdapter extends XmlAdapter<Wrapper, Map<String, Object>> {
#Override
public Map<String, Object> unmarshal(Wrapper value) throws Exception {
System.out.println("INSIDE UNMARSHALLING METHOD TEST");
final Map<String, Object> others = new HashMap<>();
for (Object obj : value.getElements()) {
final Element element = (Element) obj;
final NodeList children = element.getChildNodes();
//Check if its direct String value field or complex
if (children.getLength() == 1) {
others.put(element.getNodeName(), element.getTextContent());
} else {
List<Object> child = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
final Node n = children.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE) {
Wrapper wrapper = new Wrapper();
List childElements = new ArrayList();
childElements.add(n);
wrapper.elements = childElements;
child.add(unmarshal(wrapper));
}
}
others.put(element.getNodeName(), child);
}
}
return others;
}
#Override
public Wrapper marshal(Map<String, Object> v) throws Exception {
Wrapper wrapper = new Wrapper();
List elements = new ArrayList();
for (Map.Entry<String, Object> property : v.entrySet()) {
if (property.getValue() instanceof Map) {
elements.add(new JAXBElement<Wrapper>(new QName(property.getKey()), Wrapper.class, marshal((Map) property.getValue())));
} else {
elements.add(new JAXBElement<String>(new QName(property.getKey()), String.class, property.getValue().toString()));
}
}
wrapper.elements = elements;
return wrapper;
}
}
#Getter
class Wrapper {
#XmlAnyElement
List elements;
}
Although it works for specific cases, I am seeing one problem by using this approach. I have created a new post for this issue.
If I get any response for that issue then I will try to update the code so it can work correctly.
I need to make a binding of my object FileDocument, which contains a reference to another object, Metadata. Metadata is -- I hope-- can have a dynamic name depending on a value on its attribute.
I have heard and used XmlAdapter (also for the Metadata class), but only for the Map case. I don't really understand how to make it work for this case.
Snippet for FileDocument:
#XmlAccessorType(XmlAccessType.FIELD)
public class FileDocument{
//...
protected List<Metadata> metadata;
//...
}
Snippet for Metadata:
#XmlType(name = "metadata")
//#XmlRootElement(name = "metaCollection")
public class Metadata {
//...
#XmlPath(".")
#XmlJavaTypeAdapter(MetaAdapter.class)
Map<String, String> map;
//I'd like to have each element of metadata depend on this attribute.
String source;
}
My desired output is something like
{
"someKeyInFileDocument" : "someValueInFileDocument",
"metadata.source1" : {
"some key inside this metadata" : "some value inside this metadata",
"more!": "more!"
},
"metadata.source2" : {
"yes, the above key" : "looks similar but also different as the above",
"this is another key!" : "inside this source2 thing"
}
}
You can use EclipseLink JAXB (MOXy)'s #XmlVariableNode extension for this use case:
Java Model
FileDocument
We will use the #XmlVariableNode annotation on the metadata field. This tells MOXy that instead of using a fixed name for the element/key that the name should be taken from the specified field/property on the referenced object.
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
#XmlAccessorType(XmlAccessType.FIELD)
public class FileDocument {
#XmlVariableNode("source")
protected List<Metadata> metadata;
}
Metadata
We will use the #XmlTransient annotation on the source field to prevent it from being marshalled (see: http://blog.bdoughan.com/2012/04/jaxb-and-unmapped-properties.html).
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Metadata {
#XmlTransient
String source;
}
Demo Code
You can run the demo code below to see that everything works.
Demo
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {FileDocument.class}, properties);
Metadata m1 = new Metadata();
m1.source = "metadata.source1";
Metadata m2 = new Metadata();
m2.source = "metadata.source2";
List<Metadata> metadata = new ArrayList<Metadata>();
metadata.add(m1);
metadata.add(m2);
FileDocument fd = new FileDocument();
fd.metadata = metadata;
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(fd, System.out);
}
}
Output
{
"metadata.source1" : {
},
"metadata.source2" : {
}
}
For More Information
You can read more about the #XmlVariableNode extension on my blog:
http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-json-schema.html
In the below format my doubt is the type mentioned with every field. Can you please suggest some solution? This is a requirement from the third party who will be consuming this.
subject":{
"type":"string",
"$":"Cabinet model number?"
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is how this can be done using MOXy's JSON-binding.
Domain Model (Root)
The #XmlElement annotation can be used to specify the type of the property. Setting the type to be Object will force a type qualified to be written out.
import javax.xml.bind.annotation.*;
public class Root {
private String subject;
#XmlElement(type=Object.class)
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
}
Demo
Since a type qualifier will be marshalled out a key will need to be written for the value. By default this will be value. You can use the JSON_VALUE_WRAPPER property to change this to $.
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(3);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
properties.put(JAXBContextProperties.JSON_VALUE_WRAPPER, "$");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Root.class}, properties);
Root root = new Root();
root.setSubject("Cabinet model number?");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code.
{
"subject" : {
"type" : "string",
"$" : "Cabinet model number?"
}
}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
I have done this using gson API from google. Wrote a custom serializer which checks the type and value and creates the JSON object based on that.
The JSON is:
{"list": [1,2,3,4,5,6,7,8,9,10]}
Here's how I implement the JAXB bean:
package com.anon.sortweb.jaxb;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;
#XmlRootElement
public class JsonBean {
#XmlElement(name="list")
private int[] list;
public JsonBean() {}
public void setList(int[] list) {
this.list = list;
}
public int[] getList() {
return list;
}
}
My web application works fine (I'm able to successfully access other resources) but this one resource (that I pass my JSON to) returns a 415 Media Type Unsupported exception.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces("text/html")
public String sortAndReturnHtml(JsonBean listBean) { ... }
How do I correctly write my JAXB bean to encapsulate a list of integers?
Thanks in advance!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Your JAXB bean is a perfectly reasonable representation of your JSON data. The JAXB (JSR-222) spec does not cover JSON binding so the answer ultimately comes down to how/if your JAX-RS implementation interprets JAXB metadata to produce/consume JSON.
Demo
Below is how it would work with MOXy.
package forum13648734;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {JsonBean.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum13648734/input.json");
JsonBean jsonBean = unmarshaller.unmarshal(json, JsonBean.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(jsonBean, System.out);
}
}
input.json/Output
{"list":[1,2,3,4,5,6,7,8,9,10]}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
I am using the JAXB that is part of the Jersey JAX-RS. When I request JSON for my output type, all my attribute names start with an asterisk like this,
This object;
package com.ups.crd.data.objects;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
#XmlType
public class ResponseDetails {
#XmlAttribute public String ReturnCode = "";
#XmlAttribute public String StatusMessage = "";
#XmlAttribute public String TransactionDate ="";
}
becomes this,
{"ResponseDetails":{"#transactionDate":"07-12-2010",
"#statusMessage":"Successful","#returnCode":"0"}
So, why are there # in the name?
Any properties mapped with #XmlAttribute will be prefixed with '#' in JSON. If you want to remove it simply annotated your property with #XmlElement.
Presumably this is to avoid potential name conflicts:
#XmlAttribute(name="foo") public String prop1; // maps to #foo in JSON
#XmlElement(name="foo") public String prop2; // maps to foo in JSON
If you are marshalling to both XML and JSON, and you don't need it as an attribute in the XML version then suggestion to use #XmlElement is the best way to go.
However, if it needs to be an attribute (rather than an element) in the XML version, you do have a fairly easy alternative.
You can easily setup a JSONConfiguration that turns off the insertion of the "#".
It would look something like this:
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBContextResolver() throws Exception {
this.context= new JSONJAXBContext(
JSONConfiguration
.mapped()
.attributeAsElement("StatusMessage",...)
.build(),
ResponseDetails.class);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
There are also some other alternatives document here:
http://jersey.java.net/nonav/documentation/latest/json.html
You have to set JSON_ATTRIBUTE_PREFIX in your JAXBContext configuration to "" which by default is "#":
properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "");