Deserialiazing list of elements with #Text annotation in Simple - java

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.

Related

How to parse pojo into simple String json using Jackson 'fasterxml.jackson', in Camel without having full class name

I'm using camel(that uses jackson to parse) & parsing my pojos in json, but I'm getting output json:
{"com.xyz.Demo":{"key1":"val1","key2":"val2","fruits":
{"#serialization":"custom","unserializable-parents":"","list":{"default":
{"size":1},"int":1,"com.xyz.demo.Fruit":{"colour":"orange","timeStamp":"2020-
01-01T02:55:45Z"}}}}}
I want just class name, not with full package.
Also don't want: "#serialization":"custom","unserializable-parents":""
Respective classes:
public class Demo {
private String key1 = null;
private String key2 = null;
private Fruits fruits = null;
}
public class Fruits extends ArrayList<Fruit> {
}
public class Fruit {
private String colour = null;
private String timeStamp = null;
}
Conversion in Camel route: .marshal().json()
Tried annotating model classes with #JsonRootName , didn't work.

Unmarshalling a List into a JavaFX SimpleListProperty

I've got a nested object hierarchy which looks like this:
Profile: contains a List<Category>
Category contains a List<Script>. It is exposed via a JavaFX SimpleListProperty, so that it can be bound to via JavaFX's data binding.
Script contains nothing but simple values.
I'm just using JAXB to marshall and unmarshall POJOs. There are no databases or XML schemas involved.
Marshalling a Profile value works fine, and generates valid XML. However, unmarshalling the same XML file later, results in each Category containing an empty List<Script>. This appears to be due to the fact that Category stores the List<Script> by using a JavaFX bindable property.
Is there a way to make JAXB properly deserialize into a SimpleListProperty that contains a custom object?
Here's a minimal sample that demonstrates the same issue.
public class Main
{
public static void main(String[] args) throws Exception
{
Script script1 = new Script();
script1.name = "Script 1";
script1.otherData = "Script 1's data";
Script script2 = new Script();
script2.name = "Script 2";
script2.otherData = "Script 2's data";
ArrayList<Script> scriptList = new ArrayList<Script>();
scriptList.add(script1);
scriptList.add(script2);
Category category1 = new Category();
category1.name = "Category 1";
category1.setCategoryScripts(scriptList);
Category category2 = new Category();
category2.name = "Category 2";
category2.setCategoryScripts(scriptList);
Profile profile = new Profile();
profile.name = "Profile 1";
profile.categories.add(category1);
profile.categories.add(category2);
JAXBContext context = JAXBContext.newInstance(Profile.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter xml = new StringWriter();
m.marshal(profile, xml);
System.out.println(xml.toString());
Profile deserializedProfile = (Profile)context
.createUnmarshaller()
.unmarshal(new StringReader(xml.toString()));
System.out.println("Profile: " + deserializedProfile.name);
for(Category cat : deserializedProfile.categories)
{
System.out.println("Category: " + cat.name);
System.out.println("Scripts:");
for(Script s : cat.getCategoryScripts())
{
System.out.printf("\nName: %s, Data: %s", s.name, s.otherData);
}
}
}
}
#XmlRootElement
class Profile
{
#XmlElement
String name;
#XmlElementWrapper
#XmlElement
ArrayList<Category> categories = new ArrayList<Category>();
}
#XmlRootElement
class Category
{
#XmlElement
String name;
ListProperty<Script> categoryScripts = new SimpleListProperty<Script>();
#XmlElementWrapper
#XmlElement
public final List<Script> getCategoryScripts() { return categoryScripts.get(); }
public final void setCategoryScripts(List<Script> value) { categoryScripts.set(FXCollections.observableArrayList(value)); }
public ListProperty<Script> categoryScriptProperty() { return categoryScripts; }
}
#XmlRootElement
class Script
{
#XmlElement
String name;
#XmlElement
String otherData;
}
One of the principles of StackOverflow is Minimal, Complete, and Verifiable questions. See https://stackoverflow.com/help/mcve
When creating such test code, I did this:
public class Test {
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Categories.class);
Categories categoriesIn = new Categories();
categoriesIn.scripts.add(new Script("Hello"));
categoriesIn.scripts.add(new Script("World"));
StringWriter xml = new StringWriter();
jaxbContext.createMarshaller().marshal(categoriesIn, xml);
System.out.println(xml.toString());
Categories categoriesOut = (Categories)jaxbContext.createUnmarshaller().unmarshal(new StringReader(xml.toString()));
System.out.println(categoriesOut.scripts.size() + " scripts:");
for (Script script : categoriesOut.scripts)
System.out.println(" " + script.name);
}
}
#XmlRootElement
class Categories {
#XmlElementWrapper(name = "Scripts")
#XmlElement(name = "Script")
List<Script> scripts = new ArrayList<>();
}
class Script {
#XmlElement(name = "name")
String name;
Script() {}
Script(String name) { this.name = name; }
}
Running it produced:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><categories><Scripts><Script><name>Hello</name></Script><Script><name>World</name></Script></Scripts></categories>
2 scripts:
Hello
World
It seems to work fine, on Java 8 at least.
Try it out. If it doesn't work, then you might need to upgrade Java to a newer version. If it works, you might use it as a baseline to see what might be different in your code.
I believe you're running into a weirdness of JAXB. Notice that you don't have an addCategoryScript method. So how is JAXB going to add Script objects to the list?
It could create a list of it's own, then give it to you with setCategoryScripts, but how would it know what kind of list to create?
It solves that dilemma by calling getCategoryScripts to get the initial (empty) list, and then add elements to it.
But then what happens if you returned a copy of the internal list?
Ah Ha! Call setCategoryScripts when the list is complete.
This means that it'll call setCategoryScripts with the list returned by getCategoryScripts.
I has an implementation that needed some special handling, so what I did was:
public List<MyObj> getMyList() {
return this.myList;
}
public void setMyList(List<MyObj> myList) {
this.myList.clear();
for (MyObj o : myList)
this.myList.add(handle(o));
}
But oops. The call to clear actually cleared the incoming objList parameter, and I ended up with nothing.
My solution was to copy the parameter list before proceeding.

Referencing a field by its ID instead of expanding it

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>

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.

How to rename root key in JSON serialization with Jackson

I am using Jackson for JSON serialization of a list of objects.
Here is what I get:
{"ArrayList":[{"id":1,"name":"test name"}]}
But I want this :
{"rootname":[{"id":1,"name":"test name"}]} // ie showing the string I want as the root name.
Below is my approach to this:
Interface:
public interface MyInterface {
public long getId();
public String getName();
}
Implementation class:
#JsonRootName(value = "rootname")
public class MyImpl implements MyInterface {
private final long id;
private String name;
public MyImpl(final long id,final name) {
this.id = id;
this.name = name;
}
// getters
}
JSon serialization:
public class MySerializer {
public static String serializeList(final List<MyInterface> lists) {
//check for null value.Throw Exception
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
return mapper.writeValueAsString(lists);
}
}
Test:
final List<MyInterface> list = new ArrayList<MyImpl>();
MyImpl item = new MyImpl(1L,"test name");
list.add(item);
final String json = MySerializer.serializeList(list);
System.out.println(json);
Here is what I get:
{"ArrayList":[{"id":1,"name":"test name"}]}
But I want this :
{"rootname":[{"id":1,"name":"test name"}]} // ie showing the string I want as the root name.
I have tried all suggested solutions I could find but failed to achieve my goal. I have looked at:
Jackson : custom collection serialization to JSON
How do I rename the root key of a JSON with Java Jackson?
Jackson : custom collection serialization to JSON
Or am I missing something? I am using jackson 1.9.12 for this. Any help in this regard is welcome.
Well, by default Jackson uses one of two annotations when trying to determine the root name to be displayed for wrapped values - #XmlRootElement or #JsonRootName. It expects this annotation to be on the type being serialized, else it will use the simple name of the type as the root name.
In your case, you are serializing a list, which is why the root name is 'ArrayList' (simple name of the type being serialized). Each element in the list may be of a type annotated with #JsonRootName, but the list itself is not.
When the root value you are trying to wrap is a collection then you need some way of defining the wrap name:
Holder/Wrapper Class
You can create a wrapper class to hold the list, with an annotation to define the desired property name (you only need to use this method when you do not have direct control of the ObjectMapper/JSON transformation process):
class MyInterfaceList {
#JsonProperty("rootname")
private List<MyInterface> list;
public List<MyInterface> getList() {
return list;
}
public void setList(List<MyInterface> list) {
this.list = list;
}
}
final List<MyInterface> lists = new ArrayList<MyInterface>(4);
lists.add(new MyImpl(1L, "test name"));
MyInterfaceList listHolder = new MyInterfaceList();
listHolder.setList(lists);
final String json = mapper.writeValueAsString(listHolder);
Object Writer
This is the preferable option. Use a configured ObjectWriter instance to generate the JSON. In particular, we are interested in the withRootName method:
final List<MyInterface> lists = new ArrayList<MyInterface>(4);
lists.add(new MyImpl(1L, "test name"));
final ObjectWriter writer = mapper.writer().withRootName("rootName");
final String json = writer.writeValueAsString(lists);
I know, I am late , but I have better approach which don't require Holder/Wrapper Class. It picks root key from annotation.
package com.test;
import com.fasterxml.jackson.annotation.JsonRootName;
#JsonRootName("Products")
public class ProductDTO {
private String name;
private String description;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Here is test class:-
package com.test;
import java.io.IOException;
import java.util.ArrayList;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ProductDTOTestCase {
#Test
public void testPersistAndFindById() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
ProductDTO productDTO = new ProductDTO();
productDTO.setDescription("Product 4 - Test");
ArrayList<ProductDTO> arrayList = new ArrayList<ProductDTO>();
arrayList.add(productDTO);
String rootName = ProductDTO.class.getAnnotation(JsonRootName.class).value();
System.out.println(mapper.writer().withRootName(rootName).writeValueAsString(arrayList));
}
}
It will give following output
{"Products":[{"name":null,"description":"Product 4 - Test"}]}
#JsonTypeName("usuarios")
#JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use= JsonTypeInfo.Id.NAME)
public class UsuarioDT extends ArrayList<Usuario> {
#JsonProperty("rowsAffected")
private Integer afectados;
public Integer getAfectados() {
return afectados;
}
public void setAfectados(Integer afectados) {
this.afectados = afectados;
}
}
You need to use this annotation at the top of the class
#JsonTypeName("rootname")

Categories

Resources