Reputation: 18405
I use a common JAXB model for JAX-WS (Metro) and JAX-RS (Jersey). I have the following request snippet:
<xs:element name="CreatedProjFolders">
<xs:complexType>
<xs:sequence>
<xs:element name="CreatedProjFolder" type="tns:CreatedProjFolder" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="parentItemId" type="tns:itemId" use="required" />
</xs:complexType>
</xs:element>
<xs:complexType name="CreateProjFolder">
<xs:attribute name="itemId" type="tns:itemId" use="required" />
...
</xs:complexType>
XJC generated this class:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"createProjFolders"
})
@XmlRootElement(name = "CreateProjFolders")
public class CreateProjFolders {
@XmlElement(name = "CreateProjFolder", required = true)
@NotNull
@Size(min = 1)
@Valid
// Name has been pluralized with JAXB bindings file
protected List<CreateProjFolder> createProjFolders;
@XmlAttribute(name = "parentItemId", required = true)
@NotNull
@Size(max = 128)
protected String parentItemId;
...
}
The appropriate JSON POST should look like:
{"parentItemId":"P5J00142301", "createProjFolders":[
{"itemId":"bogus"}
]}
but actually must look like:
{"parentItemId":"P5J00142301", "CreateProjFolder":[
{"itemId":"bogus"}
]}
How can rename the property name for JSON only resembling the one in Java (protected List<CreateProjFolder> createProjFolders
)?
Upvotes: 3
Views: 2325
Reputation: 18405
After reading Blaise's post and the blog, it took me two days to come up with a working solution. First of all, the current status of MOXyJsonProvider
and ConfigurableMoxyJsonProvider
make it a pain-in-the-ass exprience to make it work and have never been designed for that.
My first test was to make a clean room implementation which is completely decoupled from Jersey and runs in a main
method.
Here is the main
method:
public static void main(String[] args) throws JAXBException {
Map<String, Object> props = new HashMap<>();
InputStream importMoxyBinding = MOXyTest.class
.getResourceAsStream("/json-binding.xml");
List<InputStream> moxyBindings = new ArrayList<>();
moxyBindings.add(importMoxyBinding);
props.put(JAXBContextProperties.OXM_METADATA_SOURCE, moxyBindings);
props.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
props.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
JAXBContext jc = JAXBContext.newInstance("my.package",
CreateProjFolders.class.getClassLoader(), props);
Unmarshaller um = jc.createUnmarshaller();
InputStream json = MOXyTest.class
.getResourceAsStream("/CreateProjFolders.json");
Source source = new StreamSource(json);
JAXBElement<CreateProjFolders> create = um.unmarshal(source, CreateProjFolders.class);
CreateProjFolders folders = create.getValue();
System.out.printf("Used JAXBContext: %s%n", jc);
System.out.printf("Unmarshalled structure: %s%n", folders);
Marshaller m = jc.createMarshaller();
m.setProperty(MarshallerProperties.INDENT_STRING, " ");
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
System.out.print("Marshalled structure: ");
m.marshal(folders, System.out);
}
Here the json-binding.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="my.package"
xml-mapping-metadata-complete="false">
<xml-schema namespace="urn:namespace"
element-form-default="QUALIFIED" />
<java-types>
<java-type name="CreateProjFolders">
<xml-root-element />
<java-attributes>
<xml-element java-attribute="projFolders" name="createProjFolders" />
</java-attributes>
</java-type>
<java-type name="CreateProjFolder">
<java-attributes>
<xml-element java-attribute="access" name="access" />
</java-attributes>
</java-type>
<java-type name="Access">
<java-attributes>
<xml-element java-attribute="productionSites" name="productionSites" />
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
and a sample input file:
{"parentItemId":"some-id",
"createProjFolders":[
{"objectNameEn":"bogus", "externalProjectId":"123456",
"access":{"productionSites":["StackOverflow"], "user":"michael-o"}}
]
}
Unmarshalling and marshalling work flawlessly. Now, how to make it work with Jersey? You can't because you cannot pass JAXBContext properties.
You need to copy MOXy's MOXyJsonProvider
and the entire source of Jersey Media MOXy except for XML stuff into a new Maven project because of the AutoDiscoverable
feature. This package will replace the original dependency.
Apply the following patches. Patches aren't perfect and can be imporoved because some code is duplicate, thus redundant but that can be done in a ticket later.
Now confiure that in your Application.class
:
InputStream importMoxyBinding = MyApplication.class
.getResourceAsStream("/json-binding.xml");
List<InputStream> moxyBindings = new ArrayList<>();
moxyBindings.add(importMoxyBinding);
final MoxyJsonConfig jsonConfig = new MoxyJsonConfig();
jsonConfig.setOxmMedatadataSource(moxyBindings);
ContextResolver<MoxyJsonConfig> jsonConfigResolver = jsonConfig.resolver();
register(jsonConfigResolver);
Now try it. After several calls on different models you will see JAXBExceptions
with 'premature end of file'. You will ask you why?! The reason is that the MOXyJsonProvider
creates and caches JAXBContexts
per domain class and not per package which means that your input stream is read several times but has already been closed after the first read. You are lost. You need to reset the stream but cannot change the inner guts of MOXy. Here is a simple solution for that:
public class ResetOnCloseInputStream extends BufferedInputStream {
public ResetOnCloseInputStream(InputStream is) {
super(is);
super.mark(Integer.MAX_VALUE);
}
@Override
public void close() throws IOException {
super.reset();
}
}
and swap your Application.class
for
moxyBindings.add(new ResetOnCloseInputStream(importMoxyBinding));
After you have felt the pain in the ass, enjoy the magic!
Final words:
OXM_METADATA_SOURCE
. Seriously?Upvotes: 1
Reputation: 149017
When MOXy is used as your JSON-binding provider the JSON keys will be the same as what is specfieid in the @XmlElement
annotations. For example when you have:
@XmlElement(name = "CreateProjFolder", required = true)
protected List<CreateProjFolder> createProjFolders;
You will get:
{"parentItemId":"P5J00142301", "CreateProjFolder":[
{"itemId":"bogus"}
]}
If you want different names in JSON than in XML you can leverage MOXy's external mapping metadata to override what has been specified in the annotations:
Upvotes: 2