JAXB: How to ignore namespace during unmarshalling XML document?

JavaXmlXml SerializationJaxb

Java Problem Overview


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>

Java Solutions


Solution 1 - Java

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 ;)

Solution 2 - Java

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);

Solution 3 - Java

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
    }
}

Solution 4 - Java

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;
		}
	}
}

Solution 5 - Java

Another way to add a default namespace to an XML Document before feeding it to JAXB is to use JDom:

  1. Parse XML to a Document
  2. Iterate through and set namespace on all Elements
  3. 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);
			}
		}
	}

Solution 6 - Java

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);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEugene YokotaView Question on Stackoverflow
Solution 1 - JavaKristoferView Answer on Stackoverflow
Solution 2 - JavaluniconView Answer on Stackoverflow
Solution 3 - JavaVonCView Answer on Stackoverflow
Solution 4 - JavaHenriqueView Answer on Stackoverflow
Solution 5 - JavamafroView Answer on Stackoverflow
Solution 6 - JavatomorrowView Answer on Stackoverflow