SimpleXML can serialize a Java Enum fine but when it comes to de-serialization, it returns null instead of creating Enum from the generated XML. Is it something I am doing wrong of Enum serialization is not supported at all?
Serialization returns this:
<TestStatus>
<status>Functional</status>
</TestStatus>
Test Enum:
#Root
public enum TestStatus {
AVAILABLE("Functional"),
NOT_AVAILABLE("Dysfunctional");
#Element
private String status;
private Status(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
}
How do you serialize your enum?
if you use it like this, it should work without problems but will return some different XML:
Example:
#Root
public class Example
{
#Element
private TestStatus status = TestStatus.AVAILABLE;
// ...
}
Test:
final File f = new File("test.xml");
Serializer ser = new Persister();
ser.write(new Example(), f);
Example m = ser.read(Example.class, f);
XML:
<example>
<status>AVAILABLE</status>
</example>
You can rename the xml-tags with annotationarguments, but the value wont be changeable.
Another (possible) solution is using a custom converter:
Annotations of the enum:
#Root()
#Convert(TestStatusConverter.class)
public enum TestStatus
{
// ...
}
Converter (Example)
public class TestStatusConverter implements Converter<TestStatus>
{
#Override
public TestStatus read(InputNode node) throws Exception
{
final String value = node.getNext("status").getValue();
// Decide what enum it is by its value
for( TestStatus ts : TestStatus.values() )
{
if( ts.getStatus().equalsIgnoreCase(value) )
return ts;
}
throw new IllegalArgumentException("No enum available for " + value);
}
#Override
public void write(OutputNode node, TestStatus value) throws Exception
{
// You can customize your xml here (example structure like your xml)
OutputNode child = node.getChild("status");
child.setValue(value.getStatus());
}
}
Test (enum):
final File f = new File("test.xml");
// Note the new Strategy
Serializer ser = new Persister(new AnnotationStrategy());
ser.write(TestStatus.AVAILABLE, f);
TestStatus ts = ser.read(TestStatus.class, f);
System.out.println(ts);
Test (class with enum):
As above but with AnnotationStrategy
You don't need to add annotations to enums, they serialize automatically.
Related
Lets say I have JSON of the following format:
{
"type" : "Foo"
"data" : {
"object" : {
"id" : "1"
"fizz" : "bizz"
...
},
"metadata" : {
...
},
"owner" : {
"name" : "John"
...
}
}
}
I am trying to avoid custom deserializer and attempting to deserialize the above JSON (called Wrapper.java) into Java POJOs. The "type" field dictates the "object" deserialization ie. type = foo means the deserialize the "object" field using the Foo.java. (if type = Bar, use Bar.java to deserialize the object field). Metadata/owner will always deserialize the same way using a simple Jackson annotated Java class for each. Is there a way to accomplish this using annotations? If not, how can this be done using a custom deserializer?
Annotations-only approach
Alternatively to the custom deserializer approach, you can have the following for an annotations-only solution (similar to the one described in Spunc's answer, but using type as an external property):
public abstract class AbstractData {
private Owner owner;
private Metadata metadata;
// Getters and setters
}
public static final class FooData extends AbstractData {
private Foo object;
// Getters and setters
}
public static final class BarData extends AbstractData {
private Bar object;
// Getters and setters
}
public class Wrapper {
private String type;
#JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = FooData.class, name = "Foo"),
#JsonSubTypes.Type(value = BarData.class, name = "Bar")
})
private AbstractData data;
// Getters and setters
}
In this approach, #JsonTypeInfo is set to use type as an external property to determine the right class to map the data property.
The JSON document can be deserialized as following:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
Custom deserializer approach
You could use a custom deserializer that checks the type property to parse the object property into the most suitable class.
First define an interface that will be implemented by Foo and Bar classes:
public interface Model {
}
public class Foo implements Model {
// Fields, getters and setters
}
public class Bar implements Model {
// Fields, getters and setters
}
Then define your Wrapper and Data classes:
public class Wrapper {
private String type;
private Data data;
// Getters and setters
}
public class Data {
#JsonDeserialize(using = ModelDeserializer.class)
private Model object;
private Metadata metadata;
private Owner owner;
// Getters and setters
}
The object field is annotated with #JsonDeserialize, indicating the deserializer that will be used for the object property.
The deserializer is defined as following:
public class ModelDeserializer extends JsonDeserializer<Model> {
#Override
public Model deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonMappingException {
// Get reference to ObjectCodec
ObjectCodec codec = jp.getCodec();
// Parse "object" node into Jackson's tree model
JsonNode node = codec.readTree(jp);
// Get value of the "type" property
String type = ((Wrapper) jp.getParsingContext().getParent()
.getCurrentValue()).getType();
// Check the "type" property and map "object" to the suitable class
switch (type) {
case "Foo":
return codec.treeToValue(node, Foo.class);
case "Bar":
return codec.treeToValue(node, Bar.class);
default:
throw new JsonMappingException(jp,
"Invalid value for the \"type\" property");
}
}
}
The JSON document can be deserialized as following:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
Alternatively to this custom deserializer, consider an annotations-only approach.
All this can be done by means of annotations.
Create an abstract superclass with the common fields like "metadata" and "owner" and their getters/setters. This class needs to be annotated with #JsonTypeInfo. It should look like:
#JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "type")
With the parameter property = "type" you specify that the class identifier will be serialized under the field type in your JSON document.
The value of the class identifier can be specified with use. Id.CLASS uses the fully-qualified Java class name. You can also use Id.MINIMAL_CLASS which is an abbreviated Java class name. To have your own identifier, use Id.NAME. In this case, you need to declare the subtypes:
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "Foo"),
#JsonSubTypes.Type(value = Bar.class, name = "Bar")
})
Implement your classes Foo and Bar by extending from the abstract superclass.
Jackson's ObjectMapper will use the additional field "type" of the JSON document for serialization and deserialization. E. g. when you deserialise a JSON string into a super class reference, it will be of the appropriate subclass:
ObjectMapper om = new ObjectMapper();
AbstractBase x = om.readValue(json, AbstractBase.class);
// x will be instanceof Foo or Bar
Complete code example (I used public fields as shortcut to not need to write getters/setters):
package test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonSubTypes;
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "Foo"),
#JsonSubTypes.Type(value = Bar.class, name = "Bar")
})
public abstract class AbstractBase {
public MetaData metaData;
public Owner owner;
#Override
public String toString() {
return "metaData=" + metaData + "; owner=" + owner;
}
public static void main(String[] args) throws IOException {
// Common fields
Owner owner = new Owner();
owner.name = "Richard";
MetaData metaData = new MetaData();
metaData.data = "Some data";
// Foo
Foo foo = new Foo();
foo.owner = owner;
foo.metaData = metaData;
CustomObject customObject = new CustomObject();
customObject.id = 20l;
customObject.fizz = "Example";
Data data = new Data();
data.object = customObject;
foo.data = data;
System.out.println("Foo: " + foo);
// Bar
Bar bar = new Bar();
bar.owner = owner;
bar.metaData = metaData;
bar.data = "A String in Bar";
ObjectMapper om = new ObjectMapper();
// Test Foo:
String foojson = om.writeValueAsString(foo);
System.out.println(foojson);
AbstractBase fooDeserialised = om.readValue(foojson, AbstractBase.class);
System.out.println(fooDeserialised);
// Test Bar:
String barjson = om.writeValueAsString(bar);
System.out.println(barjson);
AbstractBase barDeserialised = om.readValue(barjson, AbstractBase.class);
System.out.println(barDeserialised);
}
}
class Foo extends AbstractBase {
public Data data;
#Override
public String toString() {
return "Foo[" + super.toString() + "; data=" + data + ']';
}
}
class Bar extends AbstractBase {
public String data;
public String toString() {
return "Bar[" + super.toString() + "; data=" + data + ']';
}
}
class Data {
public CustomObject object;
#Override
public String toString() {
return "Data[object=" + object + ']';
}
}
class CustomObject {
public long id;
public String fizz;
#Override
public String toString() {
return "CustomObject[id=" + id + "; fizz=" + fizz + ']';
}
}
class MetaData {
public String data;
#Override
public String toString() {
return "MetaData[data=" + data + ']';
}
}
class Owner {
public String name;
#Override
public String toString() {
return "Owner[name=" + name + ']';
}
}
I think it is rather straight-forward. You probably have a super class that has properties for metadata and owner, so rather than making it truly generic, you could substitute T for your super class. But basically, you will have to parse the name of the class from the actual JSON string, which in your example would look something like this:
int start = jsonString.indexOf("type");
int end = jsonString.indexOf("data");
Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2)); // that of course, is approximate - based on how you format JSON
and overall code could be something like this:
public static <T> T deserialize(String xml, Object obj)
throws JAXBException {
T result = null;
try {
int start = jsonString.indexOf("type");
int end = jsonString.indexOf("data");
Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2));
JAXBContextFactory factory = JAXBContextFactory.getInstance();
JAXBContext jaxbContext = factory.getJaxBContext(actualClass);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
// this will create Java object
try (StringReader reader = new StringReader(xml)) {
result = (T) jaxbUnmarshaller.unmarshal(reader);
}
} catch (JAXBException e) {
log.error(String
.format("Exception while deserialising the object[JAXBException] %s\n\r%s",
e.getMessage()));
}
return result;
}
Consider the following json, getting from an public API:
anyObject : {
attributes: [
{
"name":"anyName",
"value":"anyValue"
},
{
"name":"anyName",
"value":
{
"key":"anyKey",
"label":"anyLabel"
}
}
]
}
As you can see, sometimes the value is a simple string and sometimes its an object. Is it somehow possible to deserialize those kind of json-results, to something like:
class AnyObject {
List<Attribute> attributes;
}
class Attribute {
private String key;
private String label;
}
How would I design my model to cover both cases. Is that possible ?
Despite being hard to manage as others have pointed out, you can do what you want. Add a custom deserializer to handle this situation. I rewrote your beans because I felt your Attribute class was a bit misleading. The AttributeEntry class in the object that is an entry in that "attributes" list. The ValueObject is the class that represents that "key"/"label" object. Those beans are below, but here's the custom deserializer. The idea is to check the type in the JSON, and instantiate the appropriate AttributeEntry based on its "value" type.
public class AttributeDeserializer extends JsonDeserializer<AttributeEntry> {
#Override
public AttributeEntry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode root = p.readValueAsTree();
String name = root.get("name").asText();
if (root.get("value").isObject()) {
// use your object mapper here, this is just an example
ValueObject attribute = new ObjectMapper().readValue(root.get("value").asText(), ValueObject.class);
return new AttributeEntry(name, attribute);
} else if (root.get("value").isTextual()) {
String stringValue = root.get("value").asText();
return new AttributeEntry(name, stringValue);
} else {
return null; // or whatever
}
}
}
Because of this ambiguous type inconvenience, you will have to do some type checking throughout your code base.
You can then add this custom deserializer to your object mapper like so:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(AttributeEntry.class, new AttributeDeserializer());
objectMapper.registerModule(simpleModule);
Here's the AttributeEntry:
public class AttributeEntry {
private String name;
private Object value;
public AttributeEntry(String name, String value) {
this.name = name;
this.value = value;
}
public AttributeEntry(String name, ValueObject attributes) {
this.name = name;
this.value = attributes;
}
/* getters/setters */
}
Here's the ValueObject:
public class ValueObject {
private String key;
private String label;
/* getters/setters */
}
I am new to the Simple framework for XML (Java) and ran into a problem serializing specific class constructs.
I have two classes:
#Root(name="static")
class StaticData {
#Attribute
private String id;
#Attribute
private String value;
...
}
and
#Root(name="listdata")
class ListData {
// Problem field
#Attribute
private StaticData ref;
#Element
private String name;
}
And receive
"TransformException: Transform of class StaticData not supported".
I want the ref-field in ListData not to expand into the static data XML structure (then #Element would be fine), but to get a reference.
<listdata ref="foo">
<name>bla bla</name>
</listdata>
where "foo" is a valid value for "id" in some StaticData object already loaded in my application.
In JAXB I would use the XmlJavaTypeAdapter annotation
#XmlAttribute(name="id")
#XmlJavaTypeAdapter(MyStaticDataAdapter.class)
but I cannot seem to find a working equivalent in Simple.
In doubt you can use a Converter to implement such a behaviour.
Here's an example:
#Root(name = "listdata")
#Convert(ListData.ListDataConverter.class)
class ListData
{
#Attribute
private StaticData ref;
#Element
private String name;
// ...
// Converter implementation
static class ListDataConverter implements Converter<ListData>
{
#Override
public ListData read(InputNode node) throws Exception
{
/*
* In case you also want to read, implement this too ...
*/
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public void write(OutputNode node, ListData value) throws Exception
{
node.setAttribute("ref", value.ref.getId());
node.getChild("name").setValue(value.name);
}
}
}
Usage:
Serializer ser = new Persister(new AnnotationStrategy());
/* ^----- important! -----^ */
ListData ld = ...
ser.write(ld, System.out); // Serialize to std out
Output
With these ListData values ...
name = abcdefg
ref = ...
id = 123
value = this is a value
you'll get ...
<listdata ref="123">
<name>def</name>
</listdata>
I use REST and i was wondering if i can tell jaxb to insert a string field "as-it-is" into the outgoing xml.
Certainly i count unpack it before returning, but i would like to save this step.
#XmlRootElement(name="unnestedResponse")
public class Response{
#Insert annotation here ;-)
private String alreadyXml;
private int otherDate; ...
}
Is there a possability to tell JAXB to just use the String as it is without escapting? I want that the client does not have to parse my response and then parse this field.
greetings,
m
You can use the #XmlAnyElement and specify a DomHandler to keep a portion of the XML document as a String.
Customer
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Customer {
private String bio;
#XmlAnyElement(BioHandler.class)
public String getBio() {
return bio;
}
public void setBio(String bio) {
this.bio = bio;
}
}
BioHandler
import java.io.*;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.DomHandler;
import javax.xml.transform.Source;
import javax.xml.transform.stream.*;
public class BioHandler implements DomHandler<String, StreamResult> {
private static final String BIO_START_TAG = "<bio>";
private static final String BIO_END_TAG = "</bio>";
private StringWriter xmlWriter = new StringWriter();
public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
return new StreamResult(xmlWriter);
}
public String getElement(StreamResult rt) {
String xml = rt.getWriter().toString();
int beginIndex = xml.indexOf(BIO_START_TAG) + BIO_START_TAG.length();
int endIndex = xml.indexOf(BIO_END_TAG);
return xml.substring(beginIndex, endIndex);
}
public Source marshal(String n, ValidationEventHandler errorHandler) {
try {
String xml = BIO_START_TAG + n.trim() + BIO_END_TAG;
StringReader xmlReader = new StringReader(xml);
return new StreamSource(xmlReader);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
For More Information
http://blog.bdoughan.com/2011/04/xmlanyelement-and-non-dom-properties.html
Following bdoughan's answer did not work for me as I encountered errors during marshalling when the text contained the '& character (e.g. in URLs or when using HTML entities such as e.g. " ").
I was able to resolve this by changing the custom DomHandler's marshal method to
public Source marshal(String et, ValidationEventHandler veh) {
Node node = new SimpleTextNode(et);
return new DOMSource(node);
}
where SimpleTextNode implements the Node interface as follows:
class SimpleTextNode implements Node {
String nodeValue = "";
#Override
public SimpleTextNode(String nodeValue) {
this.nodeValue = nodeValue;
}
#Override
public short getNodeType() {
return TEXT_NODE;
}
// the remaining methods of the Node interface are not needed during marshalling
// you can just use the code template of your IDE...
...
}
PS: I would have loved to leave this as a comment to bdoughan's answer, but unfortunately I have way too little reputation :-(
I'm currently using the simple XML library, and the tutorial didn't have a runnable example for ElementLists. http://simple.sourceforge.net/home.php
I have an example class:
#Root
public class Example {
#ElementList
private List<String> text;
#Attribute
private int index;
public Example() {
super();
}
public Example(List<String> text, int index) {
this.text = text;
this.index = index;
}
public List<String> getMessage() {
return text;
}
public int getId() {
return index;
}
}
And a simple class for running:
public class M {
public static void main(String[] args) throws Exception {
Serializer serializer = new Persister();
List<String> l = new LinkedList<String>();
l.add("f");
l.add("f");
Example example = new Example(l, 123);
File result = new File("example.xml");
serializer.write(example, result);
}
}
The XML that I generate is:
<example index="123">
<text class="java.util.LinkedList">
<string>f</string>
<string>f</string>
</text>
</example>
Why am I getting the class="java.util.LinkedList"? I'm confused on how remove this attribute.
You can use the VisitorStrategy to intercept the serialization of the object.
Strategy strategy = new VisitorStrategy(new Visitor() {
#Override
public void write(Type type, NodeMap<OutputNode> node) throws Exception {
if ("text".equals(node.getName())){
node.remove("class");
}
}
...
});
I was working on the same problem and I got a way to avoid the 'class' attribute.
Instead of using #ElementList like this:
#ElementList(name="Files", entry="File")
You can use #Path annotation with the #ElementList as follows:
#Path(value="Files")
#ElementList(inline=true, entry="File")
You can use an Implementation for an #ElementList List, example :
LinkedList instead of List.
#ElementList
private LinkedList<String> texts;
This will avoid class attribute not wanted.