Assume an XML feed consisting of multiple messages, such as:
<feed>
<message type="FOO_TYPE">
<!-- possibly some message content here -->
</message>
<!-- more messages -->
</feed>
feed
and message
get translated into two different Java classes, respectively; type
gets translated to a Java enum
.
Deserialization code:
// custom strategy to avoid name collisions (we use `class` as an attribute name)
Strategy XML_STRATEGY = new TreeStrategy("simpleXmlClass", "simpleXmlLength");
Serializer XML_SERIALIZER = new Persister(XML_STRATEGY);
Feed feed = XML_SERIALIZER.read(Feed.class, inputStream);
Now if the feed contains a single message with an unknown type
(i.e. one that is not defined for the respective enum
), an exception will be thrown. If I wrap the call to Serializer#read()
into an exception handler, that will cause the whole feed to be discarded.
What I want to achieve is to substitute a default value (or possibly null) whenever an unknown value is encountered (and possibly do some further filtering higher up, but that is out of scope for now). However, I don’t know where to plug that logic into the Strategy
/Serializer
construct, and the docs do not help much either.
A typical exception looks like this:
java.lang.IllegalArgumentException: No enum constant org.traffxml.traff.TraffEvent.Type.CONSTRUCTION_BRIDGE_DEMOLITION
at java.lang.Enum.valueOf(Enum.java:238)
at org.simpleframework.xml.transform.EnumTransform.read(EnumTransform.java:58)
at org.simpleframework.xml.transform.EnumTransform.read(EnumTransform.java:29)
at org.simpleframework.xml.transform.Transformer.read(Transformer.java:106)
at org.simpleframework.xml.core.Support.read(Support.java:372)
at org.simpleframework.xml.core.PrimitiveFactory.getInstance(PrimitiveFactory.java:105)
at org.simpleframework.xml.core.Primitive.readTemplate(Primitive.java:231)
at org.simpleframework.xml.core.Primitive.read(Primitive.java:171)
at org.simpleframework.xml.core.Primitive.read(Primitive.java:126)
at org.simpleframework.xml.core.Composite.readVariable(Composite.java:623)
at org.simpleframework.xml.core.Composite.readInstance(Composite.java:573)
at org.simpleframework.xml.core.Composite.readAttribute(Composite.java:497)
at org.simpleframework.xml.core.Composite.readAttributes(Composite.java:413)
at org.simpleframework.xml.core.Composite.access$300(Composite.java:59)
at org.simpleframework.xml.core.Composite$Injector.read(Composite.java:1432)
at org.simpleframework.xml.core.Composite.read(Composite.java:201)
at org.simpleframework.xml.core.Composite.read(Composite.java:148)
at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
at org.simpleframework.xml.core.CompositeInlineList.read(CompositeInlineList.java:190)
at org.simpleframework.xml.core.CompositeInlineList.read(CompositeInlineList.java:167)
at org.simpleframework.xml.core.CompositeInlineList.read(CompositeInlineList.java:124)
at org.simpleframework.xml.core.Composite.readVariable(Composite.java:623)
at org.simpleframework.xml.core.Composite.readInstance(Composite.java:573)
at org.simpleframework.xml.core.Composite.readUnion(Composite.java:549)
at org.simpleframework.xml.core.Composite.readElement(Composite.java:532)
at org.simpleframework.xml.core.Composite.readElements(Composite.java:445)
at org.simpleframework.xml.core.Composite.readSection(Composite.java:327)
at org.simpleframework.xml.core.Composite.readElements(Composite.java:443)
at org.simpleframework.xml.core.Composite.access$400(Composite.java:59)
at org.simpleframework.xml.core.Composite$Injector.read(Composite.java:1433)
at org.simpleframework.xml.core.Composite.read(Composite.java:201)
at org.simpleframework.xml.core.Composite.read(Composite.java:148)
at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
at org.simpleframework.xml.core.CompositeInlineList.read(CompositeInlineList.java:190)
at org.simpleframework.xml.core.CompositeInlineList.read(CompositeInlineList.java:167)
at org.simpleframework.xml.core.CompositeInlineList.read(CompositeInlineList.java:124)
at org.simpleframework.xml.core.Composite.readVariable(Composite.java:623)
at org.simpleframework.xml.core.Composite.readInstance(Composite.java:573)
at org.simpleframework.xml.core.Composite.readUnion(Composite.java:549)
at org.simpleframework.xml.core.Composite.readElement(Composite.java:532)
at org.simpleframework.xml.core.Composite.readElements(Composite.java:445)
at org.simpleframework.xml.core.Composite.access$400(Composite.java:59)
at org.simpleframework.xml.core.Composite$Injector.read(Composite.java:1433)
at org.simpleframework.xml.core.Composite.read(Composite.java:201)
at org.simpleframework.xml.core.Composite.read(Composite.java:148)
at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
at org.simpleframework.xml.core.Persister.read(Persister.java:625)
at org.simpleframework.xml.core.Persister.read(Persister.java:606)
at org.simpleframework.xml.core.Persister.read(Persister.java:584)
at org.simpleframework.xml.core.Persister.read(Persister.java:543)
at com.example.myowncode.MyClass.doStuff(MyClass.java:42)
Since the last exceptions in the stack include some from EnumTransformer
, which is an implementation of Transform<Enum>
, I was wondering if I could plug my custom Transform
type for that particular enum into the code; I jut don’t know how to achieve that.
Assuming the enum is called MyEnum, first write a transform class for it. The class must implement Transform<MyEnum>
; we will call it MyEnumTransform
here. Implement a custom read
method like this:
public MyEnum read(String value) throws Exception {
try {
return MyEnum.valueOf(value);
} catch (IllegalArgumentException e) {
return MyEnum.FALLBACK_VALUE; // or null, at your option
}
}
You also need to implement write
, which you can copy straight from EnumTransform
:
public String write(MyEnum value) throws Exception {
return value.name();
}
Now change your deserialization code as follows:
// custom strategy to avoid name collisions (we use `class` as an attribute name)
RegistryMatcher XML_MATCHER = new RegistryMatcher();
XML_MATCHER.bind(MyEnum.class, MyEnumTransform.class);
Strategy XML_STRATEGY = new TreeStrategy("simpleXmlClass", "simpleXmlLength");
Serializer XML_SERIALIZER = new Persister(XML_STRATEGY, XML_MATCHER);
Feed feed = XML_SERIALIZER.read(Feed.class, inputStream);
Essentially you are providing a custom matcher to your serializer. Without that, the serializer will fall back to an EmptyMatcher
instance (created on the fly), which returns null as the transform for anything, causing Simple XML to fall back to default transforms for the type (and hit an exception upon encountering an illegal value for an enum).
You may then need to do some further consistency checks upon deserializing a message with a null/default type (such as throwing out the message altogether), but that is out of scope here.