JAXB Serialization of the interface for XML problems (<String, ISomeInterface> map does not work)
I am trying to serialize an interface for XML using JAXB 2.2.4, but when I have an interface inside a Map <> object, it explodes and gives me an error:
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 numbers IllegalAnnotationExceptions com.test.IInterface2 is an interface and JAXB cannot process interfaces. This problem is related to the following location: at com.test.IInterface2 in public java.util.Map com.test.Interface1Impl.getI2 () at com.test.Interface1Impl com.test.IInterface2 does not have a no-arg constructor by default. This problem is related to the following location: at com.test.IInterface2 at public java.util.Map com.test.Interface1Impl.getI2 () in com.test.Interface1Impl
This code has been tested and works if I delete Map <>, and even got it to work if I use List <>, but there is something in Map <> that JAXB doesn't like.
Here is the code I'm running, let me know if you know how to fix it!
package com.test; import java.io.StringWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.annotation.XmlSeeAlso; @XmlSeeAlso({Interface2Impl.class}) public class main { /** * @param args */ public static void main(String[] args) { IInterface1 i1 = new Interface1Impl(); i1.setA("SET A VALUE"); i1.setB("Set B VALUE"); IInterface2 i2 = new Interface2Impl(); i2.setC("X"); i2.setD("Y"); i1.getI2().put("SOMVAL",i2); String retval = null; try { StringWriter writer = new StringWriter(); JAXBContext context = JAXBContext.newInstance(Interface1Impl.class, Interface2Impl.class); Marshaller m = context.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.marshal(i1, writer); retval = writer.toString(); } catch (JAXBException ex) { //TODO: Log the error here! retval = ex.toString(); } System.out.println(retval); } } package com.test; import java.util.Map; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import com.sun.xml.bind.AnyTypeAdapter; @XmlRootElement @XmlJavaTypeAdapter(AnyTypeAdapter.class) public interface IInterface1 { Map<String,IInterface2> getI2(); String getA(); String getB(); void setA(String a); void setB(String b); void setI2(Map<String,IInterface2> i2); } package com.test; import java.util.HashMap; import java.util.Map; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Interface1Impl implements IInterface1 { Map<String,IInterface2> i2 = new HashMap<String,IInterface2>(); String a; String b; public Interface1Impl() { } public String getA() { return a; } public void setA(String a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public Map<String,IInterface2> getI2() { return i2; } public void setI2(Map<String,IInterface2> i2) { this.i2 = i2; } } package com.test; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import com.sun.xml.bind.AnyTypeAdapter; @XmlRootElement @XmlJavaTypeAdapter(AnyTypeAdapter.class) public interface IInterface2 { String getC(); String getD(); void setC(String c); void setD(String d); } package com.test; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Interface2Impl implements IInterface2 { String c; String d; public Interface2Impl() { } public String getC() { return c; } public void setC(String c) { this.c = c; } public String getD() { return d; } public void setD(String d) { this.d = d; } } To get the following conclusion, you can do the following (see below):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <interface1Impl> <a>SET A VALUE</a> <b>Set B VALUE</b> <i2> <entry> <key>SOMVAL</key> <value> <c>X</c> <d>Y</d> </value> </entry> </i2> </interface1Impl> I2Adapter
We will use the XmlAdapter to handle Map<String, IInterface2> . XmlAdapter is a JAXB mechanism that converts an object that JAXB cannot display into one that it can.
package com.test; import java.util.*; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.adapters.XmlAdapter; public class I2Adapter extends XmlAdapter<I2Adapter.AdaptedI2, Map<String, IInterface2>> { @Override public AdaptedI2 marshal(Map<String, IInterface2> v) throws Exception { if(null == v) { return null; } AdaptedI2 adaptedI2 = new AdaptedI2(); for(Map.Entry<String,IInterface2> entry : v.entrySet()) { adaptedI2.entry.add(new Entry(entry.getKey(), entry.getValue())); } return adaptedI2; } @Override public Map<String, IInterface2> unmarshal(AdaptedI2 v) throws Exception { if(null == v) { return null; } Map<String, IInterface2> map = new HashMap<String, IInterface2>(); for(Entry entry : v.entry) { map.put(entry.key, entry.value); } return map; } public static class AdaptedI2 { public List<Entry> entry = new ArrayList<Entry>(); } public static class Entry { public Entry() { } public Entry(String key, IInterface2 value) { this.key = key; this.value = value; } public String key; @XmlElement(type=Interface2Impl.class) public IInterface2 value; } } Interface1Impl
The @XmlJavaTypeAdapter annotation @XmlJavaTypeAdapter used to register an XmlAdapter . In this example, we register it in the i2 property.
package com.test; import java.util.*; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement public class Interface1Impl implements IInterface1 { Map<String, IInterface2> i2 = new HashMap<String, IInterface2>(); String a; String b; public Interface1Impl() { } public String getA() { return a; } public void setA(String a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } @XmlJavaTypeAdapter(I2Adapter.class) public Map<String, IInterface2> getI2() { return i2; } public void setI2(Map<String, IInterface2> i2) { this.i2 = i2; } } Additional Information
- http://blog.bdoughan.com/2011/05/jaxb-and-interface-fronted-models.html
- http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html
Below is the rest of your model with JAXB annotations removed from non-model classes:
Main
package com.test; import java.io.StringWriter; import javax.xml.bind.*; public class main { /** * @param args */ public static void main(String[] args) { IInterface1 i1 = new Interface1Impl(); i1.setA("SET A VALUE"); i1.setB("Set B VALUE"); IInterface2 i2 = new Interface2Impl(); i2.setC("X"); i2.setD("Y"); i1.getI2().put("SOMVAL", i2); String retval = null; try { StringWriter writer = new StringWriter(); JAXBContext context = JAXBContext.newInstance(Interface1Impl.class, Interface2Impl.class); Marshaller m = context.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.marshal(i1, writer); retval = writer.toString(); } catch (JAXBException ex) { // TODO: Log the error here! retval = ex.toString(); } System.out.println(retval); } } IInterface1
package com.test; import java.util.Map; public interface IInterface1 { Map<String, IInterface2> getI2(); String getA(); String getB(); void setA(String a); void setB(String b); void setI2(Map<String, IInterface2> i2); } Iinterface2
package com.test; public interface IInterface2 { String getC(); String getD(); void setC(String c); void setD(String d); } Interface2Impl
package com.test; public class Interface2Impl implements IInterface2 { String c; String d; public Interface2Impl() { } public String getC() { return c; } public void setC(String c) { this.c = c; } public String getD() { return d; } public void setD(String d) { this.d = d; } }