I've got the following problem and I've no clue how to solve this.
I've got a list of different types of points based on a common interface.
I am using Java XStream to marshall and unmarshall those classes.
public static void main(String[] args) {
List<IPoint> listOfPoint = new ArrayList<IPoint>();
listOfPoint.add(new PointTypeA(0.1));
listOfPoint.add(new PointTypeB(0.2));
listOfPoint.add(new PointTypeA(0.3));
PointSet ps = new PointSet(1, listOfPoint);
XStream xstream = new XStream(new StaxDriver());
xstream.processAnnotations(PointTypeA.class);
xstream.processAnnotations(PointTypeB.class);
xstream.processAnnotations(PointSet.class);
String xml = xstream.toXML(ps);
System.out.println(xml);
}
When I print my objects in XML format I get the following result:
<set id="1">
<typeA>
<xCoordinate>0.1</xCoordinate>
</typeA>
<typeB>
<xCoordinate>0.2</xCoordinate>
</typeB>
<typeA>
<xCoordinate>0.3</xCoordinate>
</typeA>
</set>
But instead of the result above, I want to have the following output:
<set id="1">
<typeA>0.1</typeA>
<typeB>0.2</typeB>
<typeA>0.3</typeA>
</set>
What I want is not to have tags like <xCoordinate>, but I want their value to be stored under the classname's tag.
I do not want to ignore the value of xCoordinate field, but I want to to have an "inline value".
Is it possible to do that?
I've tried converters with no success, and I've got no idea how to solve this.
My classes are:
public interface IPoint {
int getSomeInformation();
}
#XStreamAlias("set")
public class PointSet {
#XStreamAsAttribute
private int id;
#XStreamImplicit
private List<IPoint> points;
public PointSet(int id, List<IPoint> points) {
super();
this.id = id;
this.points = points;
}
}
#XStreamAlias("typeA")
public class PointTypeA implements IPoint {
private double xCoordinate;
public PointTypeA(double d) {
super();
this.xCoordinate = d;
}
}
#XStreamAlias("typeB")
public class PointTypeB implements IPoint {
private double xCoordinate;
public PointTypeB(double d) {
super();
this.xCoordinate = d;
}
}
Converter for your point class is rather simple.
public static class CoordConverter implements Converter
{
public boolean canConvert(Class clazz)
{
return PointTypeA.class == clazz;
}
public void marshal(Object object, HierarchicalStreamWriter hsw, MarshallingContext mc)
{
PointTypeA obj = (PointTypeA) object;
hsw.setValue(String.valueOf(obj.xCoordinate));
}
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc)
{
double val = Double.parseDouble(hsr.getValue());
PointTypeA obj = new PointTypeA(val);
return obj;
}
}
You can register it with
xstream.registerConverter(new CoordConverter());
Of course, this converter is valid for PointTypeA class but you can easily extend above code for other classes you need and/or write more generalized version.
Related
I'd like to deserialize an object from YAML with the following properties, using Jackson in a Spring Boot application:
Abstract class Vehicle, implemented by Boat and Car
For simplicity, imagine both have a name, but only Boat has also a seaworthy property, while Car has a top-speed.
mode-of-transport:
type: boat
name: 'SS Boatface'
seaworthy: true
----
mode-of-transport:
type: car`
name: 'KITT'
top-speed: 123
This all works fine in my annotated subclasses using #JsonTypeInfo and #JsonSubTypes!
Now, I'd like to create a shorthand using only a String value, which should create a Car by default with that name:
mode-of-transport: 'KITT'
I tried creating my own custom serializer, but got stuck on most of the relevant details. Please help me fill this in, if this is the right approach:
public class VehicleDeserializer extends StdDeserializer<Merger> {
/* Constructors here */
#Override
public Vehicle deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (/* it is an OBJECT */){
// Use the default polymorphic deserializer
} else if (/* it is a STRING */) {
Car car = new Car();
car.setName( /* the String value */ );
return car;
}
return ???; /* what to return here? */
}
}
I found these 2 answers for inspiration, but it looks like combining it with polymorphic types makes it more difficult: How do I call the default deserializer from a custom deserializer in Jackson and Deserialize to String or Object using Jackson
A few things are different than the solutions offered in those questions:
I am processing YAML, not JSON. Not sure about the subtle differences there.
I have no problem hardcoding the 'default' type for Strings inside my Deserializer, hopefully making it simpler.
This was actually easier than I thought to solve it. I got it working using the following:
Custom deserializer implementation:
public class VehicleDeserializer extends StdDeserializer<Vehicle> {
public VehicleDeserializer() {
super(Vehicle.class);
}
#Override
public Vehicle deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
if (jp.currentToken() == JsonToken.VALUE_STRING) {
Car car = new Car();
car.setName(jp.readValueAs(String.class));
return car;
}
return jp.readValueAs(Vehicle.class);
}
}
To avoid circular dependencies and to make the custom deserializer work with the polymorphic #JsonTypeInfo and #JsonSubTypes annotations I kept those annotations on the class level of Vehicle, but put the following annotations on the container object I am deserializing:
public class Transport {
#JsonDeserialize(using = VehicleDeserializer.class)
#JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
private Vehicle modeOfTransport;
// Getter, setters
}
This means that by default a Vehicle is deserialized as a polymorphic object, unless explicitly specified to deserialize it using my custom deserializer. This deserializer will then in turn defer to the polymorphism if the input is not a String.
Hopefully this will help someone running into this issue :)
So there is a solution that requires you to handle the jackson errors using a DeserializationProblemHandler (since you want to parse the same type using different inputs, this is not achieved easily using regular means):
public class MyTest {
#Test
public void doTest() throws JsonParseException, JsonMappingException, IOException {
final ObjectMapper om = new ObjectMapper();
om.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleMissingInstantiator(final DeserializationContext ctxt, final Class<?> instClass, final JsonParser p, final String msg) throws IOException {
if (instClass.equals(Car.class)) {
final JsonParser parser = ctxt.getParser();
final String text = parser.getText();
switch (text) {
case "KITT":
return new Car();
}
}
return NOT_HANDLED;
}
#Override
public JavaType handleMissingTypeId(final DeserializationContext ctxt, final JavaType baseType, final TypeIdResolver idResolver, final String failureMsg) throws IOException {
// if (baseType.isTypeOrSubTypeOf(Vehicle.class)) {
final JsonParser parser = ctxt.getParser();
final String text = parser.getText();
switch (text) {
case "KITT":
return TypeFactory.defaultInstance().constructType(Car.class);
}
return super.handleMissingTypeId(ctxt, baseType, idResolver, failureMsg);
}
});
final Container objectValue = om.readValue(getObjectJson(), Container.class);
assertTrue(objectValue.getModeOfTransport() instanceof Car);
final Container stringValue = om.readValue(getStringJson(), Container.class);
assertTrue(stringValue.getModeOfTransport() instanceof Car);
}
private String getObjectJson() {
return "{ \"modeOfTransport\": { \"type\": \"car\", \"name\": \"KITT\", \"speed\": 1}}";
}
private String getStringJson() {
return "{ \"modeOfTransport\": \"KITT\"}";
}
}
class Container {
private Vehicle modeOfTransport;
public Vehicle getModeOfTransport() {
return modeOfTransport;
}
public void setModeOfTransport(final Vehicle modeOfTransport) {
this.modeOfTransport = modeOfTransport;
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
#JsonSubTypes({
#Type(name = "car", value = Car.class)
})
abstract class Vehicle {
protected String type;
protected String name;
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
#JsonTypeName("car")
class Car extends Vehicle {
private int speed;
public int getSpeed() {
return speed;
}
public void setSpeed(final int speed) {
this.speed = speed;
}
}
Note that I used JSON, not YAML, and you need to add your other subtypes as well.
I have a simple pojo class:
#XmlRootElement
public static class MyClass {
private double f; //[0-1]
#XmlAttribute
//#XmlJavaTypeAdapter(FixedFloatingPointXmlAdapter.class) <-- has issues
public double getF() {
return f;
}
}
field f holds values in the range [0,1], which can sometimes be small. I'd like to avoid seeing things like 1.234E-7 (scientific notation) and would like to set the format used to print field f.
I've seen other answers that suggest using an XmlAdapter for this, like so:
public class FixedFloatingPointXmlAdapter extends XmlAdapter<String, Double> {
private static final DecimalFormat FORMAT = new DecimalFormat("0.00000000");
#Override
public Double unmarshal(String v) throws Exception {
return v == null ? null : Double.parseDouble(v);
}
#Override
public String marshal(Double v) throws Exception {
return v == null ? null : FORMAT.format(v);
}
}
but the problem with this is that the value would be printed out as a json STRING (so "0.25000000" instead of 0.25000000) - because the XmlAdapter returns a String (not because FORMAT places quotes. it doesnt)
is there any JAXB/moxy annotation that would allow me to control the formatting without turning the field into a json string?
This solution is using transformation annotations of EclipseLink MOXy.
import org.eclipse.persistence.oxm.annotations.XmlTransformation;
import org.eclipse.persistence.oxm.annotations.XmlWriteTransformer;
#XmlRootElement
public static class MyClass {
#XmlAttribute
#XmlReadTransformer(transformerClass=FixedFloatingTransformer.class)
#XmlWriteTransformer(xmlPath="#f", transformerClass=FixedFloatingTransformer.class)
private double f; //[0-1]
public double getF() {
return f;
}
}
Here is the implementation of transformerClass.
import java.text.DecimalFormat;
import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping;
import org.eclipse.persistence.mappings.transformers.AttributeTransformer;
import org.eclipse.persistence.mappings.transformers.FieldTransformer;
import org.eclipse.persistence.sessions.Record;
import org.eclipse.persistence.sessions.Session;
public class FixedFloatingTransformer implements FieldTransformer, AttributeTransformer {
private AbstractTransformationMapping mapping;
private static DecimalFormat FORMAT = new DecimalFormat("0.00000000");
public Object buildAttributeValue(Record r, Object obj, Session arg2) {
return r.get(mapping.getFields().get(0));
}
public Object buildFieldValue(Object instance, String xpath, Session session) {
Object o2 = mapping.getAttributeValueFromObject(instance);
if (o2 instanceof Number) {
return ((DecimalFormat)FORMAT.clone()).format((Number)o2);
}
return null;
}
public void initialize(AbstractTransformationMapping mapping) {
this.mapping = mapping;
}
}
Update in 2016/12/13
The #XmlSchemaType are referenced when moxy unmarshal the object to JSON string.
If the name is boolean or number type, moxy will outputed the value string without quoted char.
Sample 1: You have to set formated value to f.
public static class MyClass {
private String f;
#XmlAttribute
#XmlSchemaType(name="double")
public String getF() {
return f;
}
}
Sample 2 : Add extra getter to return formatted value.
public static class MyClass {
private double f;
#XmlTransient //preventing the mapping
public double getF() {
return f;
}
#XmlAttribute(name="f")
#XmlSchemaType(name="double")
public String getFAsString() {
return new DecimalFormat("0.00000000").format(f);
}
}
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.
I'm using JAXB to generate a xml file from my java objects (xml export), as well as the other way arround (xml import).
In some cases I'm using a "magic-number" to initialize a integer class attribute, because 0 is also valid an I want to initialize the attribute and mark it as "not-yet-edited".
In the xml output generated from JAXB I would be happy if this magic-number is not existing. Is it possible to provide JAXB with something like a mapping information?
Please have a look at the example.
Example:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="my-root")
public class ExampleClass {
/** integer value which represents empty */
public static final int EMPTY_INT = Integer.MAX_VALUE;
/** my id */
#XmlElement(name="id")
private int mMyId = EMPTY_INT;
public void setMyId(int myId) {
mMyId = myId;
}
public int getMyId() {
return mMyId;
}
}
JAXB generates someting like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<my-root>
<id>2147483647</id>
</my-root>
What I want is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<my-root>
<id></id>
</my-root>
I need to tell JAXB to generate "nothing" (see example) if the attribute value is EMPTY_INT and the other way arround (import).
Is that possible somehow?
Or are there other ways to reach that goal?
Thank you for your help.
Steffen
UPDATE:
Based on the answers I tried the following:
Note: The code is shorted (e. g. without imports)
1) add a class: Mydapter
public class MyAdapter extends XmlAdapter<String, Integer> {
#Override
public Integer unmarshal(String val) throws Exception {
System.out.println("Debug1");
return Integer.parseInt(val);
}
#Override
public String marshal(Integer val) throws Exception {
System.out.println("Debug2");
if (val == Integer.MAX_VALUE) {
return "";
} else {
return val.toString();
}
}
}
2) adapted ExampleClass to use "Integer" instead of "int" and annotade it
#XmlJavaTypeAdapter(MyAdapter.class)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="my-root")
public class ExampleClass {
/** integer value which represents empty */
public static final int EMPTY_INT = Integer.MAX_VALUE;
/** my id */
#XmlElement(name="id")
private Integer mMyId = EMPTY_INT;
public void setMyId(int myId) {
mMyId = myId;
}
public int getMyId() {
return mMyId;
}
}
3) Code performing the xml export
public class XMLImportExport {
public static void exportToXml(File xmlFile) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ExampleClass.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(new ExampleClass(), xmlFile);
}
}
4) xml output is still
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<my-root>
<id>2147483647</id>
</my-root>
Thank you,
Steffen
Annotate mMyId with #XmlJavaTypeAdapter(YourAdapter.class) and then write an adapter to do the job. Something like this (untested) :
public class YourAdapter extends XmlAdapter<String, Integer> {
public Integer unmarshal(String val) throws Exception {
return Integer.parseInt(val);
}
public String marshal(Integer val) throws Exception {
if ( val == Integer.MAX_VALUE) {
return "";
} else {
return val.toString();
}
}
}
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.