I'm using Jersey (1.9.1) and utilizing JAXB for my objects, and trying to add a field which is simply a JSON object, ideally, of type Jackson's JsonNode, but I can also work with jettison's JSONObject (or even GSON's JsonObject/JsonElement)
The reason for that is since I want to allow a 'free-form' json to be sent (as the obj element in our example), in addition to other elements on that same object (which are using JAXB annotations)
My object:
import org.codehaus.jettison.json.JSONObject;
@XmlRootElement(name="moo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Moo {
@XmlElement
JSONObject obj;
...
}
My resource:
@Path("/moo")
@POST
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public Response postMsg(Moo e) {
System.out.println(e);
return Response.status(200).entity(e).build();
}
Payload:
{"obj": {"my": "custom", "object": 1, "here": [1, 2, 3] } }
When executing a POST request with the above - obj
is an empty JSONObject
. The same thing happened when I tried using Jackson's JsonNode
and GSON's JsonElement
/JsonObject
.
However, when I use JSONObject
as the arg for the method, its able to parse it
@Path("/moo")
@POST
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public Response postMsg(JSONObject e) {
System.out.println(e);
return Response.status(200).entity(e).build();
}
I assume this happens since Jersey uses its own com.sun.jersey.json.impl.provider.entity.JSONObjectProvider
to for marshalling, while JAXB uses its own marshller.
Is there a way around this while still using JAXB's annotation (for other fields)? I tried playing around with the a custom XmlAdapter
(using the @XmlJavaTypeAdapter
annotation) without any success (since the Object I got as the ValueType
is a DOM element)
My final target is to have the field obj
as Jackson representation (JsonNode
).
Any directions would be much appreciated.
If I got it right, you want to embedd a raw json for free-from values.
@XmlRootElement(name="moo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Moo {
Object json;
@JsonRawValue
public String getJson() {
// default raw value: null or "[]"
return json == null ? null : json.toString();
}
public void setJson(JsonNode node) {
this.json = node;
}
}
Taken from here.
There are multiple problems First of all this code
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.sun.xml.bind.marshaller.CharacterEscapeHandler;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.validation.Schema;
import javax.xml.validation.TypeInfoProvider;
import javax.xml.validation.Validator;
import javax.xml.validation.ValidatorHandler;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
public class TestObjectNode {
@XmlRootElement(name = "moo")
@XmlAccessorType(XmlAccessType.FIELD)
static class Moo {
@JsonProperty("obj")
@XmlElement
@XmlJavaTypeAdapter(ObjMarshaller.class)
JsonNode obj;
}
static final ObjectMapper JSON_MAPPER = new ObjectMapper();
static final XmlMapper XML_MAPPER = (XmlMapper) new XmlMapper()
.disable(SerializationFeature.WRAP_ROOT_VALUE)
.disable(DeserializationFeature.UNWRAP_ROOT_VALUE);
public static void main(String[] args) throws Exception {
final String value = "{\"obj\": {\"my\": \"custom\", \"object\": 1, \"here\": [1, 2, 3] } }";
Moo moo = JSON_MAPPER.readValue(value, Moo.class);
System.out.println(moo.obj);
saveToFile(moo);
System.out.println(XML_MAPPER.writeValueAsString(moo));
moo = readFromFile();
System.out.println(moo.obj);
}
private static void saveToFile(Moo moo) throws Exception {
File file = new File("moo.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(Moo.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.setProperty(CharacterEscapeHandler.class.getName(),
new CharacterEscapeHandler() {
@Override
public void escape(char[] chars, int i, int i1, boolean b, Writer writer) throws IOException {
writer.write(chars, i, i1);
}
});
jaxbMarshaller.marshal(moo, file);
jaxbMarshaller.marshal(moo, System.out);
}
private static Moo readFromFile() throws Exception {
File file = new File("moo.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(Moo.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
MySchema mySchema = new MySchema();
unmarshaller.setSchema(mySchema);
Moo moo = (Moo) unmarshaller.unmarshal(file);
String objectNode = mySchema.validatorHandler.getObjectNodeString();
System.out.println("objectNode = " + objectNode);
JsonNode jsonNode = XML_MAPPER.readValue(objectNode, ObjectNode.class);
moo.obj = jsonNode;
return moo;
}
private static class ObjMarshaller extends XmlAdapter<String, JsonNode> {
@Override
public JsonNode unmarshal(String v) throws Exception {
throw new IllegalArgumentException("i'm never here///");
}
@Override
public String marshal(JsonNode v) throws Exception {
return XML_MAPPER.writeValueAsString(v);
}
}
static class MySchema extends Schema {
final ValidatorHandlerImpl validatorHandler = new ValidatorHandlerImpl();
@Override
public Validator newValidator() {
return null;
}
@Override
public ValidatorHandler newValidatorHandler() {
return validatorHandler;
}
}
static class ValidatorHandlerImpl extends ValidatorHandler {
private final StringBuilder objectNode = new StringBuilder();
private boolean insideObjectNode = false;
public String getObjectNodeString() {
return objectNode.toString();
}
@Override
public void setContentHandler(ContentHandler receiver) {
}
@Override
public ContentHandler getContentHandler() {
return null;
}
@Override
public void setErrorHandler(ErrorHandler errorHandler) {
}
@Override
public ErrorHandler getErrorHandler() {
return null;
}
@Override
public void setResourceResolver(LSResourceResolver resourceResolver) {
}
@Override
public LSResourceResolver getResourceResolver() {
return null;
}
@Override
public TypeInfoProvider getTypeInfoProvider() {
return null;
}
@Override
public void setDocumentLocator(Locator locator) {
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void endDocument() throws SAXException {
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if(localName.equals("ObjectNode")) {
insideObjectNode = true;
}
if (insideObjectNode) {
objectNode.append("<" + localName + ">");
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (insideObjectNode) {
objectNode.append("</" + localName + ">");
}
if(localName.equals("ObjectNode")) {
insideObjectNode = false;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (insideObjectNode) {
objectNode.append(ch, start, length);
}
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
}
@Override
public void skippedEntity(String name) throws SAXException {
}
}
}
Provides following output
{"my":"custom","object":1,"here":[1,2,3]}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<moo>
<obj><ObjectNode><my>custom</my><object>1</object><here>1</here><here>2</here><here>3</here></ObjectNode></obj>
</moo>
<Moo><obj><my>custom</my><object>1</object><here>1</here><here>2</here><here>3</here></obj></Moo>
objectNode = <ObjectNode><my>custom</my><object>1</object><here>1</here><here>2</here><here>3</here></ObjectNode>
{"my":"custom","object":"1","here":"3"}
So, this because Jackson XML could not parse "here" as array, unfortunately. So, we should serialize it in different way:
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.sun.xml.bind.marshaller.CharacterEscapeHandler;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.validation.Schema;
import javax.xml.validation.TypeInfoProvider;
import javax.xml.validation.Validator;
import javax.xml.validation.ValidatorHandler;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
public class TestObjectNode {
@XmlRootElement(name = "moo")
@XmlAccessorType(XmlAccessType.FIELD)
static class Moo {
@JsonProperty("obj")
@XmlElement
@XmlJavaTypeAdapter(ObjMarshaller.class)
JsonNode obj;
}
static final ObjectMapper JSON_MAPPER = new ObjectMapper();
static final XmlMapper XML_MAPPER = (XmlMapper) new XmlMapper()
.disable(SerializationFeature.WRAP_ROOT_VALUE)
.disable(DeserializationFeature.UNWRAP_ROOT_VALUE);
public static void main(String[] args) throws Exception {
final String value = "{\"obj\": {\"my\": \"custom\", \"object\": 1, \"here\": [1, 2, 3] } }";
Moo moo = JSON_MAPPER.readValue(value, Moo.class);
System.out.println(moo.obj);
saveToFile(moo);
System.out.println(XML_MAPPER.writeValueAsString(moo));
moo = readFromFile();
System.out.println(moo.obj);
}
private static void saveToFile(Moo moo) throws Exception {
File file = new File("moo.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(Moo.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.setProperty(CharacterEscapeHandler.class.getName(),
new CharacterEscapeHandler() {
@Override
public void escape(char[] chars, int i, int i1, boolean b, Writer writer) throws IOException {
writer.write(chars, i, i1);
}
});
jaxbMarshaller.marshal(moo, file);
jaxbMarshaller.marshal(moo, System.out);
}
private static Moo readFromFile() throws Exception {
File file = new File("moo.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(Moo.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
MySchema mySchema = new MySchema();
unmarshaller.setSchema(mySchema);
Moo moo = (Moo) unmarshaller.unmarshal(file);
String objectNode = mySchema.validatorHandler.getObjectNodeString();
System.out.println("objectNode = " + objectNode);
JsonNode jsonNode = XML_MAPPER.readValue(objectNode, ObjectNode.class);
Object hereObject = jsonNode.get("here");
if (hereObject instanceof TextNode) {
String hereArray = ((TextNode) hereObject).asText().replace("<![CDATA[", "").replace("]]>", "");
ArrayNode here = JSON_MAPPER.readValue(hereArray, ArrayNode.class);
((ObjectNode) jsonNode).put("here", here);
}
moo.obj = jsonNode;
return moo;
}
private static class ObjMarshaller extends XmlAdapter<String, JsonNode> {
@Override
public JsonNode unmarshal(String v) throws Exception {
return null;
}
@Override
public String marshal(JsonNode v) throws Exception {
Object hereObject = v.get("here");
if (hereObject instanceof ArrayNode) {
ArrayNode here = (ArrayNode) hereObject;
final StringBuilder value = new StringBuilder("<![CDATA[").append('[');
for (int i = 0; i < here.size(); ++i) {
if (i > 0) {
value.append(',');
}
value.append(here.get(i));
}
value.append(']').append("]]>");
((ObjectNode) v).put("here", value.toString());
}
return XML_MAPPER.writeValueAsString(v);
}
}
static class MySchema extends Schema {
final ValidatorHandlerImpl validatorHandler = new ValidatorHandlerImpl();
@Override
public Validator newValidator() {
return null;
}
@Override
public ValidatorHandler newValidatorHandler() {
return validatorHandler;
}
}
static class ValidatorHandlerImpl extends ValidatorHandler {
private final StringBuilder objectNode = new StringBuilder();
private boolean insideObjectNode = false;
public String getObjectNodeString() {
return objectNode.toString();
}
@Override
public void setContentHandler(ContentHandler receiver) {
}
@Override
public ContentHandler getContentHandler() {
return null;
}
@Override
public void setErrorHandler(ErrorHandler errorHandler) {
}
@Override
public ErrorHandler getErrorHandler() {
return null;
}
@Override
public void setResourceResolver(LSResourceResolver resourceResolver) {
}
@Override
public LSResourceResolver getResourceResolver() {
return null;
}
@Override
public TypeInfoProvider getTypeInfoProvider() {
return null;
}
@Override
public void setDocumentLocator(Locator locator) {
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void endDocument() throws SAXException {
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if(localName.equals("ObjectNode")) {
insideObjectNode = true;
}
if (insideObjectNode) {
objectNode.append("<" + localName + ">");
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (insideObjectNode) {
objectNode.append("</" + localName + ">");
}
if(localName.equals("ObjectNode")) {
insideObjectNode = false;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (insideObjectNode) {
objectNode.append(ch, start, length);
}
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
}
@Override
public void skippedEntity(String name) throws SAXException {
}
}
}
Results:
{"my":"custom","object":1,"here":[1,2,3]}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<moo>
<obj><ObjectNode><my>custom</my><object>1</object><here><![CDATA[[1,2,3]]]></here></ObjectNode></obj>
</moo>
<Moo><obj><my>custom</my><object>1</object><here><![CDATA[[1,2,3]]]></here></obj></Moo>
objectNode = <ObjectNode><my>custom</my><object>1</object><here><![CDATA[[1,2,3]]]></here></ObjectNode>
{"my":"custom","object":"1","here":[1,2,3]}
This is only example, how the exact your task could be achieved. You see that it needed some hard coding, so maybe it's better to think about another approach. Maybe @XmlAnyElement could help you. Or use text variant from previous answer. Also if you delete CharacterEscapeHandler.class you would receive escaped xml string.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With