Referencing a field by its ID instead of expanding it - java

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>

Related

Java Enum Accepted Values

An enum data type is defined as an attribute of a class.
public class Foo {
public enum Direction {
NORTH("north"),
EAST("east"),
SOUTH("south");
public final String label;
private Direction(String label) {
this.label = label;
}
}
private Directory direction;
...
}
When I parse a Json data to match the class, I get an error
String "east": not one of the values accepted for Enum class: [NORTH, EAST, SOUTH, WEST]
This problem can be resolved by changing the enum data to all low case. If I want to use the Java enum data type convention, what is needed to resolve the problem?
If you are using Jackson to deserialise the Foo class, you could:
public class Foo {
public enum Direction {
NORTH("north"),
EAST("east"),
SOUTH("south");
#JsonValue
public final String label;
private Direction(String label) {
this.label = label;
}
}
private Direction direction;
// getter, setter for direction must exist
}
// then deserialise by:
ObjectMapper mapper = new ObjectMapper();
String json = "{\"direction\":\"north\"}";
Foo f = mapper.readValue(json, Foo.class);
This will result in a Foo object with a Direction.NORTH field.
For other possibilities when using Jackson check https://www.baeldung.com/jackson-serialize-enums

Spring MongoRepository#findall: ConverterNotFoundException

I have two simple documents MyDoc and NestedDoc
MyDoc:
public class MyDoc {
#Id
private final String id;
private final NestedDoc nested;
public MyDoc (MyIdentifier myIdentifier, Nested nested) {
this(myIdentifier.toString(),
new NestedDoc(nested.getIdentifier(), nested.getStp()));
}
#PersistenceConstructor
public MyDoc (String id, NestedDoc nestedDoc) {
this.id = id;
this.nestedDoc = nestedDoc;
}
// ...
}
NestedDoc:
public class NestedDoc {
private final String identifier;
private final Stp stp; // is an enum
#PersistenceConstructor
public NestedDocDoc (String identifier, Stp stp) {
this.identifier = identifier;
this.stp = type;
}
// ...
}
There is a straight forward repository:
public interface MyMongoRepo extends MongoRepository<MyDoc, String> {
default MyDoc findByIdentifier (MyIdentifier identifier) {
return findOne(identifier.toString());
}
}
Now, when I call MyMongoRepo#findAll I get
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [java.lang.String]
to type [com.xmpl.NestedDoc]
Expected Outpout:
When I call MyMongoRepo#findByIdentifier (like in a RestController) I get something like:
{
id: 123,
nested: {
identifier: "abc",
stp: "SOME_CONSTANT",
}
}
and MyMongoRepo#findAll should return an array that contains all known MyDocs.
In addition to the problem, it would be interesting to know why a converter is needed in the first place. What happens under the hood that requires a String to be converted?
You have mongo document in your database which look like below
{
id: 1,
nested: "somevalue"
}
and spring fails to convert String into NestedDoc object.
Fix/Remove the document and you should be fine.

Deserialiazing list of elements with #Text annotation in Simple

I have two classes, Package and ModelRefObj. Package contains two sets of ModelRefObj.
I'm using Simple framework to parse their instances from XML, so I've created some JUnit tests. I'm able to parse ModelRefObj XML, but I'm getting the following exception when trying to parse a Package:
org.simpleframework.xml.core.ValueRequiredException: Empty value for #org.simpleframework.xml.Text(empty=, data=false, required=true) on field 'value' private java.lang.String cz.semanta.coc.domain.cognos.ModelRefObj.value in class cz.semanta.coc.domain.cognos.ModelRefObj at line 1
at org.simpleframework.xml.core.Composite.readInstance(Composite.java:580)
at org.simpleframework.xml.core.Composite.readText(Composite.java:467)
at org.simpleframework.xml.core.Composite.access$200(Composite.java:59)
at org.simpleframework.xml.core.Composite$Builder.read(Composite.java:1381)
...
Here is the XML I'm trying to parse:
<package>
<name>GO Sales (nalysis)</name>
<visible>
<refObj>[go_sales]</refObj>
<refObj>[Filters and calculations].[Returns]</refObj>
</visible>
<hidden>
<refObj>[gosales].[BRANCH].[BRANCH_CODE]</refObj>
<refObj>[gosales].[BRANCH].[ADDRESS1]</refObj>
<refObj>[gosales].[BRANCH].[CITY]</refObj>
</hidden>
</package>
Here are my annotated classes:
#Root(name = "package")
public class Package {
#Element
private String name;
#ElementList(name = "visible", entry = "refObj", type = ModelRefObj.class)
private Set<ModelRefObj> visibleRefObjs;
#ElementList(name = "hidden", entry = "refObj", type = ModelRefObj.class)
private Set<ModelRefObj> hiddenRefObjs;
Package() { }
...
}
#Root(name = "refObj")
public class ModelRefObj {
#Text
private String value;
ModelRefObj() { }
public ModelRefObj(String value) {
this.value = value;
}
...
}
I have implemented the classes you have and used the example xml you provided.
I created a main function to test
public static void main(String args[]) throws Exception {
Serializer serializer = new Persister(new Format("<?xml version=\"1.0\" encoding= \"UTF-8\" ?>"));
File source = new File("sample.xml");
Package p = serializer.read(Package.class, source);
System.out.println(p.name);
}
The output is
GO Sales (nalysis)
Inspecting the object p in debug mode shows it has the two Sets with two and three elements.
Your code works fine for me.

Deserializing callback of non-field element

I have the following XML structure:
<key>
<element>
someValue
</element>
<!-- lots of other elements which should be deserialized into the class -->
<other>
someOtherValue
</other>
</key>
And i use Simple to deserialize it to the following Java class:
#Root(name = "key", strict = false)
public class Key {
#Element(name = "element")
private String element;
// lots of more fields should be deserialized from xml
}
Note that the class does not have a field for the other element. I do not need the value of it in the class, but in an other place. How can i intercept the parsing and extract the value of this other element?
You can do it a number of ways, best is to use a Converter or a Strategy though. Converter is the easiest of the two.
I think the Strategy approach doesn't work, because they use the annotated class as the XML schema, and what is not present in the schema that will not be processed (visitors can't visit).
Converters can be used as follows:
#Root(name = "key", strict = false)
#Convert(KeyConverter.class)
public class Key {
private String element;
public Key(String elementValue) {
element = elementValue;
}
}
The converter stores the value during conversion:
public class KeyConverter implements Converter<Key> {
private String otherValue;
#Override
public Key read(InputNode node) throws Exception {
String elementValue = node.getNext("element").getValue().trim();
otherValue = node.getNext("other").getValue().trim();
return new Key(elementValue);
}
#Override
public void write(OutputNode arg0, Key arg1) throws Exception {
throw new UnsupportedOperationException();
}
/**
* #return the otherValue
*/
public String getOtherValue() {
return otherValue;
}
}
Putting together:
Registry registry = new Registry();
KeyConverter keyConverter = new KeyConverter();
registry.bind(Key.class, keyConverter);
Persister serializer = new Persister(new RegistryStrategy(registry));
Key key = serializer.read(Key.class, this.getClass().getResourceAsStream("key.xml"));
// Returns the value "acquired" during the last conversion
System.out.println(keyConverter.getOtherValue());
This is not too elegant, but might be suitable for your need.
I could not make a solution with a Stragegy or Converter as ng and Katona suggested. However, i made a workaround, which works, but not too nice.
/* package */ class SerializedKey extends Key {
#Element(name = "other", required = false)
private int mOtherValue;
public int getOtherValue() {
return mOtherValue;
}
}
...
Serializer serializer = new Persister();
SerializedKey key = serializer.read(SerializedKey.class, mInputStream);
int otherValue = key.getOtherValue();
Outside of the serialization package, i use Key as static type, so i simply forget that another field is in that object. When i persist my data, i also persist as a Key, so the mOtherValue is not connected to the class anymore. As you can see SerializedKey class is package-private, so i do not expose this helper class to any other component of my application.

Sourceforge SimpleXML Enum serialization

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.

Categories

Resources