I am creating XML files using JAXB based on the XSD template generated classes.
But the XML output is not expected one.
public class ProcessXMLService {
public static void main(String[] args) throws JAXBException {
JAXBContext context = JAXBContext.newInstance("com.ford.xml.poc");
StringBuilder xmlSB = new StringBuilder();
try {
FileWriter out = new FileWriter("C:\\gopal\\sales.xml");
int count=0;
while(count<2){
StringWriter xmlStr = new StringWriter();
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty("jaxb.formatted.output",Boolean.FALSE);
marshaller.marshal(getMSGBody("DONE"), xmlStr);
xmlSB.append(xmlStr.toString()+"\n");
count++;
}
out.write(xmlSB.toString());
out.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
static JAXBElement<MessageBodyType> getMSGBody(String serviceName){
ObjectFactory factory = new ObjectFactory();
RequestInfoType reqInfoType = factory.createRequestInfoType();
reqInfoType.setRequesterName("ETL Sales");
reqInfoType.setRequestId("123");
//JAXBElement<RequestInfoType> reqInfoJaxB = factory.createRequestInfo(reqInfoType);
SalesDataType saleDataType = factory.createSalesDataType();
saleDataType.setGlobalSourceRecordId("c0a460e0-60aa-4bd5-9155-5645b4afe1de");// Update GSRID
//JAXBElement<SalesDataType> saleDataJaxB = factory.createSaleData(saleDataType);
MessageBodyType msgBdType = factory.createMessageBodyType();
msgBdType.setRequestInfo(reqInfoType);
msgBdType.getSaleData().add(saleDataType);
JAXBElement<MessageBodyType> msgBd = factory.createMessageBody(msgBdType);
return msgBd;
}
}
Getting the below XML output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns4:MessageBody
xmlns:ns2="urn:ford/identifier/v1.0"
xmlns:ns4="urn:ford/Consumer/Sales/v1.0"
xmlns:ns3="urn:ford/crfcommon/v1.0">
<ns3:RequestInfo>
<RequestId>123</RequestId>
<RequesterName>ETL Sales</RequesterName>
</ns3:RequestInfo>
<SaleData>
<ns2:GlobalSourceRecordId>c0a460e0-60aa-4bd5-9155-5645b4afe1de</ns2:GlobalSourceRecordId>
</SaleData>
</ns4:MessageBody>
But expected XML output is :
<?xml version="1.0" encoding="UTF-8"?>
<tns:MessageBody
xmlns:tns="urn:ford/Consumer/Sales/v1.0">
<com:RequestInfo
xmlns:com="urn:ford/crfcommon/v1.0">
<com:RequestId>123</com:RequestId>
<com:RequesterName>ETL Sales</com:RequesterName>
</com:RequestInfo>
<tns:SaleData>
<ns1:GlobalSourceRecordId
xmlns:ns1="urn:ford/identifier/v1.0">c0a460e0-60aa-4bd5-9155-5645b4afe1de
</ns1:GlobalSourceRecordId>
</tns:SaleData>ns4:MessageBody>
I'm trying to write a method to pretty print JSON Strings, using MOXy. So what I want is to have a method like this
public String formatJson(String input) { ... }
I think the way to go is to parse the String to a generic Object (Something like a SAX-Document, or the kind), and then marshal this Object back to JSON using some formatting properties (which is not the problem :-) ).
The Problem is, when reading the JSON-String-Input, I don't have a Class to unmarshal to (as I want the method to be as generic as possible).
[edited] GSON and Jackson examples removed, as only MOXy is the question.
I tried this:
public static String toFormattedJson(final String jsonString) {
String formatted;
try {
JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[] { JAXBElement.class }, null);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setProperty(MEDIA_TYPE, MediaType.APPLICATION_JSON);
unmarshaller.setProperty(JSON_INCLUDE_ROOT, true);
StringReader reader = new StringReader(jsonString);
Object element = unmarshaller.unmarshal(reader); // Exception is thrown here
formatted = toFormattedJson(element);
} catch (final JAXBException e) {
formatted = jsonString;
}
return formatted;
}
but I get an this Exception
javax.xml.bind.UnmarshalException
- with linked exception:
[java.lang.ClassCastException: org.eclipse.persistence.internal.oxm.record.SAXUnmarshallerHandler cannot be cast to org.eclipse.persistence.internal.oxm.record.UnmarshalRecord]
So how can I read an arbitrary JSON String in to a Java Object, if I don't have any Class for that specific String?
Update:
This is the method used to format an Object into a JSON String:
private static String toFormattedJson(Object obj) {
String result;
try (StringWriter writer = new StringWriter()) {
final JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[] { obj.getClass() }, null);
final Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
marshaller.setProperty(MarshallerProperties.JSON_REDUCE_ANY_ARRAYS, false);
marshaller.setProperty(MarshallerProperties.JSON_MARSHAL_EMPTY_COLLECTIONS, false);
marshaller.setProperty(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, false);
marshaller.setProperty(JAXBContextProperties.JSON_INCLUDE_ROOT, true);
marshaller.marshal(obj, writer);
writer.flush();
result = writer.toString();
} catch (JAXBException | IOException e) {
result = obj.toString();
}
return result;
}
And using now the code from below (Martin Vojtek), when I try to format
String jsonString = "{\"p\" : [ 1, 2, 3]}";
I get:
{
"p" : "1"
}
You can specify String as the unmarshal target:
public static void main(String[] args) {
System.out.println(toFormattedJson("[{\"test\":true}, \"ok\", [\"inner\",1]]"));
}
public static String toFormattedJson(final String jsonString) {
String formatted;
try {
JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[] { JAXBElement.class }, null);
System.out.println("jaxbContext="+jaxbContext);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setProperty(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
unmarshaller.setProperty(JAXBContextProperties.JSON_INCLUDE_ROOT, true);
StringReader reader = new StringReader(jsonString);
Object element = unmarshaller.unmarshal(new StreamSource(reader), String.class);
formatted = toFormattedJsonElement(element);
} catch (final JAXBException e) {
e.printStackTrace();
formatted = jsonString;
}
return formatted;
}
private static String toFormattedJsonElement(Object element) {
return "formatted: " + element;
}
I've tried this:
package-info.java
#XmlSchema(xmlns=
{
#XmlNs(prefix="Person", namespaceURI="sample.url.something"),
#XmlNs(prefix="xsi", namespaceURI="http://www.w3.org/2001/XMLSchema-instance")
})
Java
#XmlRootElement(name = "Person:sampleData")
public class Person {
private static String path = "files/test.xml";
#XmlElement()
public String Name;
#XmlElement()
public int Age;
public Person(){}
public Person(String name, int age){
this.Name = name;
this.Age = age;
}
public static String PersonToXMLString(Person person) throws JAXBException
{
JAXBContext jc = JAXBContext.newInstance(Person.class);
StringWriter sw = new StringWriter();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "somelocation.xsd");
marshaller.marshal(person, sw);
return sw.toString();
}
public static Person XMLStringToPerson() throws JAXBException
{
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Person Person = (Person) unmarshaller.unmarshal(new File(path));
return Person;
}
public static void WriteXMLStringFile(String xml) throws IOException
{
File file = new File(path);
try (FileOutputStream fop = new FileOutputStream(file)) {
if (!file.exists()) {
file.createNewFile();
}
byte[] contentInBytes = xml.getBytes();
fop.write(contentInBytes);
fop.flush();
fop.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static String ReadXmlStringFromFile() throws IOException
{
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line.trim());
}
return sb.toString();
}
public static void main(String[] args) throws JAXBException, IOException
{
Person user = new Person("User",23);
String xml = user.PersonToXMLString(user);
System.out.println(xml);
user.WriteXMLStringFile(xml);
xml = user.ReadXmlStringFromFile();
//used to unmarshall xml to Person object
Person person = user.XMLStringToPerson();
System.out.println(person.Name);
}
}
XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person:sampleData xmlns:Person="sample.url.something" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>User</Name>
<Age>23</Age>
</Person:sampleData>
If i do something like this i get exception on unmarshalling:
Exception in thread "main" javax.xml.bind.UnmarshalException: unexpected element (uri:"sample.url.something", local:"sampleData").
Expected elements are <{}Person:sampleData>`
FYI: I can not modify the XML.
Any help gratefully received!
You can do the following:
package-info.java
Your #XmlSchema annotation should look something like the following on your package-info class. Since elementFormDefault is specified as UNQUALIFIED the namespace will only be applied to global elements (in JAXB those corresponding to the #XmlRootElement). Note a JAXB impl is not required to use the prefix included in the #XmlSchema when marshalling XML.
#XmlSchema(
elementFormDefault=XmlNsForm.UNQUALIFIED,
namespace="sample.url.something",
xmlns={
#XmlNs(prefix="Person", namespaceURI="sample.url.something")
}
)
package com.example;
import javax.xml.bind.annotation.*;
Person
The #XmlRootElement annotation should not include the prefix.
package com.example;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="sampleData")
public class Person {
For More Information
You can read more about controlling namespace prefixes in JAXB on my blog:
http://blog.bdoughan.com/2011/11/jaxb-and-namespace-prefixes.html
I am using JAXB to marshal out objects and current my console output differs from my generated XML file:
Console:
<!-- My awesome comment -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Cat>
<name>Toby</name>
</Cat>
Generated:
<Cat>
<name>Toby</name>
</Cat>
I expect the output in the console to match what is generated within Cat.xml however this is not the case. My question is what is incorrect in my approach to generate a "correct" Cat.xml?
Minimum functioning program below:
public class CatDriver{
public static void main(String[] args) throws JAXBException, IOException,
ParserConfigurationException, TransformerException {
Cat cat = new Cat();
cat.setName("Toby");
JAXBContext context = JAXBContext.newInstance(Cat.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setProperty(
"com.sun.xml.bind.xmlHeaders",
"<!-- My awesome comment"
+ " --> \n <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
m.marshal(cat, System.out);
Writer w = null;
try {
w = new FileWriter("C:/test/Cat.xml");
m.marshal(cat, w);
} finally {
try {
w.close();
} catch (Exception e) {
}
}
}
}
#XmlRootElement(name = "Cat")
class Cat {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Take a look on this article: http://blog.bdoughan.com/2010/09/jaxb-xml-infoset-preservation.html
It is not exactly what you need but probably it is even better approach.
Cat cat = new Cat();
cat.setName( "Toby" );
JAXBContext context = JAXBContext.newInstance( Cat.class );
Marshaller m = context.createMarshaller();
m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
m.setProperty( Marshaller.JAXB_FRAGMENT, Boolean.FALSE );
m.marshal( cat, System.out );
Writer w = null;
try
{
w = new FileWriter( "Cat.xml" );
w.append( "<!-- My awesome comment -->" );
w.flush();
m.marshal( cat, w );
}
finally
{
try
{
w.close();
}
catch ( Exception e )
{
}
}
My schema specifies a namespace, but the documents don't. What's the simplest way to ignore namespace during JAXB unmarshalling (XML -> object)?
In other words, I have
<foo><bar></bar></foo>
instead of,
<foo xmlns="http://tempuri.org/"><bar></bar></foo>
Here is an extension/edit of VonCs solution just in case someone doesn´t want to go through the hassle of implementing their own filter to do this. It also shows how to output a JAXB element without the namespace present. This is all accomplished using a SAX Filter.
Filter implementation:
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;
public class NamespaceFilter extends XMLFilterImpl {
private String usedNamespaceUri;
private boolean addNamespace;
//State variable
private boolean addedNamespace = false;
public NamespaceFilter(String namespaceUri,
boolean addNamespace) {
super();
if (addNamespace)
this.usedNamespaceUri = namespaceUri;
else
this.usedNamespaceUri = "";
this.addNamespace = addNamespace;
}
#Override
public void startDocument() throws SAXException {
super.startDocument();
if (addNamespace) {
startControlledPrefixMapping();
}
}
#Override
public void startElement(String arg0, String arg1, String arg2,
Attributes arg3) throws SAXException {
super.startElement(this.usedNamespaceUri, arg1, arg2, arg3);
}
#Override
public void endElement(String arg0, String arg1, String arg2)
throws SAXException {
super.endElement(this.usedNamespaceUri, arg1, arg2);
}
#Override
public void startPrefixMapping(String prefix, String url)
throws SAXException {
if (addNamespace) {
this.startControlledPrefixMapping();
} else {
//Remove the namespace, i.e. don´t call startPrefixMapping for parent!
}
}
private void startControlledPrefixMapping() throws SAXException {
if (this.addNamespace && !this.addedNamespace) {
//We should add namespace since it is set and has not yet been done.
super.startPrefixMapping("", this.usedNamespaceUri);
//Make sure we dont do it twice
this.addedNamespace = true;
}
}
}
This filter is designed to both be able to add the namespace if it is not present:
new NamespaceFilter("http://www.example.com/namespaceurl", true);
and to remove any present namespace:
new NamespaceFilter(null, false);
The filter can be used during parsing as follows:
//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Unmarshaller u = jc.createUnmarshaller();
//Create an XMLReader to use with our filter
XMLReader reader = XMLReaderFactory.createXMLReader();
//Create the filter (to add namespace) and set the xmlReader as its parent.
NamespaceFilter inFilter = new NamespaceFilter("http://www.example.com/namespaceurl", true);
inFilter.setParent(reader);
//Prepare the input, in this case a java.io.File (output)
InputSource is = new InputSource(new FileInputStream(output));
//Create a SAXSource specifying the filter
SAXSource source = new SAXSource(inFilter, is);
//Do unmarshalling
Object myJaxbObject = u.unmarshal(source);
To use this filter to output XML from a JAXB object, have a look at the code below.
//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Marshaller m = jc.createMarshaller();
//Define an output file
File output = new File("test.xml");
//Create a filter that will remove the xmlns attribute
NamespaceFilter outFilter = new NamespaceFilter(null, false);
//Do some formatting, this is obviously optional and may effect performance
OutputFormat format = new OutputFormat();
format.setIndent(true);
format.setNewlines(true);
//Create a new org.dom4j.io.XMLWriter that will serve as the
//ContentHandler for our filter.
XMLWriter writer = new XMLWriter(new FileOutputStream(output), format);
//Attach the writer to the filter
outFilter.setContentHandler(writer);
//Tell JAXB to marshall to the filter which in turn will call the writer
m.marshal(myJaxbObject, outFilter);
This will hopefully help someone since I spent a day doing this and almost gave up twice ;)
I have encoding problems with XMLFilter solution, so I made XMLStreamReader to ignore namespaces:
class XMLReaderWithoutNamespace extends StreamReaderDelegate {
public XMLReaderWithoutNamespace(XMLStreamReader reader) {
super(reader);
}
#Override
public String getAttributeNamespace(int arg0) {
return "";
}
#Override
public String getNamespaceURI() {
return "";
}
}
InputStream is = new FileInputStream(name);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithoutNamespace xr = new XMLReaderWithoutNamespace(xsr);
Unmarshaller um = jc.createUnmarshaller();
Object res = um.unmarshal(xr);
I believe you must add the namespace to your xml document, with, for example, the use of a SAX filter.
That means:
Define a ContentHandler interface with a new class which will intercept SAX events before JAXB can get them.
Define a XMLReader which will set the content handler
then link the two together:
public static Object unmarshallWithFilter(Unmarshaller unmarshaller,
java.io.File source) throws FileNotFoundException, JAXBException
{
FileReader fr = null;
try {
fr = new FileReader(source);
XMLReader reader = new NamespaceFilterXMLReader();
InputSource is = new InputSource(fr);
SAXSource ss = new SAXSource(reader, is);
return unmarshaller.unmarshal(ss);
} catch (SAXException e) {
//not technically a jaxb exception, but close enough
throw new JAXBException(e);
} catch (ParserConfigurationException e) {
//not technically a jaxb exception, but close enough
throw new JAXBException(e);
} finally {
FileUtil.close(fr); //replace with this some safe close method you have
}
}
In my situation, I have many namespaces and after some debug I find another solution just changing the NamespaceFitler class. For my situation (just unmarshall) this work fine.
import javax.xml.namespace.QName;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;
import com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector;
public class NamespaceFilter extends XMLFilterImpl {
private SAXConnector saxConnector;
#Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if(saxConnector != null) {
Collection<QName> expected = saxConnector.getContext().getCurrentExpectedElements();
for(QName expectedQname : expected) {
if(localName.equals(expectedQname.getLocalPart())) {
super.startElement(expectedQname.getNamespaceURI(), localName, qName, atts);
return;
}
}
}
super.startElement(uri, localName, qName, atts);
}
#Override
public void setContentHandler(ContentHandler handler) {
super.setContentHandler(handler);
if(handler instanceof SAXConnector) {
saxConnector = (SAXConnector) handler;
}
}
}
Another way to add a default namespace to an XML Document before feeding it to JAXB is to use JDom:
Parse XML to a Document
Iterate through and set namespace on all Elements
Unmarshall using a JDOMSource
Like this:
public class XMLObjectFactory {
private static Namespace DEFAULT_NS = Namespace.getNamespace("http://tempuri.org/");
public static Object createObject(InputStream in) {
try {
SAXBuilder sb = new SAXBuilder(false);
Document doc = sb.build(in);
setNamespace(doc.getRootElement(), DEFAULT_NS, true);
Source src = new JDOMSource(doc);
JAXBContext context = JAXBContext.newInstance("org.tempuri");
Unmarshaller unmarshaller = context.createUnmarshaller();
JAXBElement root = unmarshaller.unmarshal(src);
return root.getValue();
} catch (Exception e) {
throw new RuntimeException("Failed to create Object", e);
}
}
private static void setNamespace(Element elem, Namespace ns, boolean recurse) {
elem.setNamespace(ns);
if (recurse) {
for (Object o : elem.getChildren()) {
setNamespace((Element) o, ns, recurse);
}
}
}
This is just a modification of lunicon's answer (https://stackoverflow.com/a/24387115/3519572) if you want to replace one namespace for another during parsing. And if you want to see what exactly is going on, just uncomment the output lines and set a breakpoint.
public class XMLReaderWithNamespaceCorrection extends StreamReaderDelegate {
private final String wrongNamespace;
private final String correctNamespace;
public XMLReaderWithNamespaceCorrection(XMLStreamReader reader, String wrongNamespace, String correctNamespace) {
super(reader);
this.wrongNamespace = wrongNamespace;
this.correctNamespace = correctNamespace;
}
#Override
public String getAttributeNamespace(int arg0) {
// System.out.println("--------------------------\n");
// System.out.println("arg0: " + arg0);
// System.out.println("getAttributeName: " + getAttributeName(arg0));
// System.out.println("super.getAttributeNamespace: " + super.getAttributeNamespace(arg0));
// System.out.println("getAttributeLocalName: " + getAttributeLocalName(arg0));
// System.out.println("getAttributeType: " + getAttributeType(arg0));
// System.out.println("getAttributeValue: " + getAttributeValue(arg0));
// System.out.println("getAttributeValue(correctNamespace, LN):"
// + getAttributeValue(correctNamespace, getAttributeLocalName(arg0)));
// System.out.println("getAttributeValue(wrongNamespace, LN):"
// + getAttributeValue(wrongNamespace, getAttributeLocalName(arg0)));
String origNamespace = super.getAttributeNamespace(arg0);
boolean replace = (((wrongNamespace == null) && (origNamespace == null))
|| ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
return replace ? correctNamespace : origNamespace;
}
#Override
public String getNamespaceURI() {
// System.out.println("getNamespaceCount(): " + getNamespaceCount());
// for (int i = 0; i < getNamespaceCount(); i++) {
// System.out.println(i + ": " + getNamespacePrefix(i));
// }
//
// System.out.println("super.getNamespaceURI: " + super.getNamespaceURI());
String origNamespace = super.getNamespaceURI();
boolean replace = (((wrongNamespace == null) && (origNamespace == null))
|| ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
return replace ? correctNamespace : origNamespace;
}
}
usage:
InputStream is = new FileInputStream(xmlFile);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithNamespaceCorrection xr =
new XMLReaderWithNamespaceCorrection(xsr, "http://wrong.namespace.uri", "http://correct.namespace.uri");
rootJaxbElem = (JAXBElement<SqgRootType>) um.unmarshal(xr);
handleSchemaError(rootJaxbElem, pmRes);