将列表解组为JavaFX SimpleListProperty

时间:2015-09-21 21:59:42

标签: java xml jaxb

我有一个嵌套的对象层次结构,如下所示:

Profile:包含List<Category>

Category包含List<Script>。它通过JavaFX SimpleListProperty公开,因此可以通过JavaFX的数据绑定绑定它。

Script只包含简单的值。

我只是使用JAXB来编组和解组POJO。没有涉及数据库或XML模式。

编组Profile值工作正常,并生成有效的XML。但是,稍后解组相同的XML文件会导致每个Category包含空List<Script>。这似乎是由于Category通过使用JavaFX可绑定属性来存储List<Script>

有没有办法让JAXB正确反序列化为包含自定义对象的SimpleListProperty?

这是展示相同问题的最小样本。

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;
}

2 个答案:

答案 0 :(得分:0)

StackOverflow的一个原则是最小,完整和可验证的问题。见https://stackoverflow.com/help/mcve

创建此类测试代码时,我这样做了:

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; }
}

运行它生成:

<?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

它至少在Java 8上运行良好。

试一试。如果它不起作用,那么您可能需要将Java升级到更新的版本。如果它有效,您可以将其用作基线,以查看代码中可能存在的不同之处。

答案 1 :(得分:0)

我相信你遇到了JAXB的奇怪之处。请注意,您没有addCategoryScript方法。那么JAXB如何将Script个对象添加到列表中呢?

它可以创建一个自己的列表,然后用setCategoryScripts给你,但是它怎么知道要创建什么类型的列表?

它通过调用getCategoryScripts来获取初始(空)列表,然后向其中添加元素来解决这个难题。

但是如果您返回内部列表的副本会发生什么? 啊哈!列表完成后,请致电setCategoryScripts

这意味着它会使用setCategoryScripts返回的列表调用getCategoryScripts

我有一个需要一些特殊处理的实现,所以我做的是:

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));
}

但是哎呀。对clear的调用实际上清除了传入的objList参数,而我最终没有任何结果。

我的解决方案是在继续之前复制参数列表。