Преобразование XML в три списка различных объектов с помощью STAX Parser

Есть ли способ использовать синтаксический анализатор STAX для эффективного анализа XML-документа с несколькими списками объектов разных классов (POJO). Точная структура моего XML выглядит следующим образом (имена классов не настоящие)

<?xml version="1.0" encoding="utf-8"?>
<root>
    <notes />
    <category_alpha>
        <list_a>
            <class_a_object></class_a_object>
            <class_a_object></class_a_object>
            <class_a_object></class_a_object>
            <class_a_object></class_a_object>
            .
            .
            .
        </list_a>
        <list_b>
            <class_b_object></class_b_object>
            <class_b_object></class_b_object>
            <class_b_object></class_b_object>
            <class_b_object></class_b_object>
            .
            .
            .
        </list_b>
    </category_alpha>
    <category_beta>
        <class_c_object></class_c_object>
        <class_c_object></class_c_object>
        <class_c_object></class_c_object>
        <class_c_object></class_c_object>
        <class_c_object></class_c_object>
        .
        .
        .
        .
        .
    </category_beta>
</root>

Я использую STAX Parser, то есть библиотеку XStream, ссылка: XStream

Он работает абсолютно нормально, пока мой XML содержит список объектов одного класса, но я не знаю, как обрабатывать XML, содержащий список объектов разных классов.

Любая помощь будет очень признательна, и, пожалуйста, дайте мне знать, если я не предоставил достаточно информации или неправильно сформулировал вопрос.


person Nick Div    schedule 11.05.2019    source источник


Ответы (3)


Вы можете использовать библиотеку разбора потока Declarative Stream Mapping (DSM), чтобы легко преобразовать сложный XML в класс Java. Он использует StAX для анализа XML.

Я пропускаю получение тега notes и добавляю поле внутри тегов class_x_object для демонстрации.

Вот XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <notes />
    <category_alpha>
        <list_a>
            <class_a_object>
                <fieldA>A1</fieldA>
            </class_a_object>
            <class_a_object>
                <fieldA>A2</fieldA>
            </class_a_object>
            <class_a_object>
                <fieldA>A3</fieldA>
            </class_a_object>

        </list_a>
        <list_b>
            <class_b_object>
                <fieldB>B1</fieldB>
            </class_b_object>
            <class_b_object>
                <fieldB>B2</fieldB>
            </class_b_object>
            <class_b_object>
                <fieldB>B3</fieldB>
            </class_b_object>
        </list_b>
    </category_alpha>
    <category_beta>
        <class_c_object>
          <fieldC>C1</fieldC>
        </class_c_object>
        <class_c_object>
          <fieldC>C2</fieldC>
        </class_c_object>
        <class_c_object>
          <fieldC>C3</fieldC>
        </class_c_object>
    </category_beta>
</root>

Прежде всего, вы должны определить сопоставление между данными XML и полями вашего класса в формате yaml или JSON.

Вот определения отображения:

result:     
   type: object
   path: /root   
   fields:
     listOfA:
       type: array
       path: .*class_a_object  # path is regex
       fields:
          fieldOfA:
            path: fieldA
     listOfB:
       type: array
       path: .*class_b_object
       fields:
          fieldOfB:
            path: fieldB 
     listOfC:
       type: array
       path: .*class_c_object
       fields:
          fieldOfC:
            path: fieldC 

Класс Java, который вы хотите десериализовать:

public class Root {
    public List<A> listOfA;
    public List<B> listOfB;
    public List<C> listOfC;

    public static class A{
        public String fieldOfA;
    }
    public static class B{
        public String fieldOfB;
    }
    public static class C{
        public String fieldOfC;
    }

}   

Код Java для анализа XML:

DSM dsm=new DSMBuilder(new File("path/to/mapping.yaml")).setType(DSMBuilder.TYPE.XML).create(Root.class);
Root root =  (Root)dsm.toObject(xmlFileContent);
// write root object as json
dsm.getObjectMapper().writerWithDefaultPrettyPrinter().writeValue(System.out, object);

Вот вывод:

{
  "listOfA" : [ {"fieldOfA" : "A1"}, {"fieldOfA" : "A2"}, {"fieldOfA" : "A3"} ],
  "listOfB" : [ {"fieldOfB" : "B1"}, {"fieldOfB" : "B2"}, "fieldOfB" : "B3"} ],
  "listOfC" : [ {"fieldOfC" : "C1"}, {"fieldOfC" : "C2"}, {"fieldOfC" : "C3"} ]
}

ОБНОВЛЕНИЕ:

Как я понял из вашего комментария, вы хотите читать большой XML-файл как поток. и обрабатывать данные, пока вы читаете файл.

DSM позволяет вам обрабатывать данные во время чтения XML.

Объявите три разные функции для обработки частичных данных.

FunctionExecutor processA=new FunctionExecutor(){
            @Override
            public void execute(Params params) {

                Root.A object=params.getCurrentNode().toObject(Root.A.class);

                // process aClass; save to db. call service etc.
            }
        };
FunctionExecutor processB=new FunctionExecutor(){
            @Override
            public void execute(Params params) {

                Root.B object=params.getCurrentNode().toObject(Root.B.class);

                // process aClass; save to db. call service etc.
            }
        };

FunctionExecutor processC=new FunctionExecutor(){
            @Override
            public void execute(Params params) {

                Root.C object=params.getCurrentNode().toObject(Root.C.class);

                // process aClass; save to db. call service etc.
            }
        };

Регистрация функции в DSM

 DSMBuilder builder = new DSMBuilder(new File("path/to/mapping.yaml")).setType(DSMBuilder.TYPE.XML);

       // register function
        builder.registerFunction("processA",processA);
        builder.registerFunction("processB",processB);
        builder.registerFunction("processC",processC);

        DSM dsm= builder.create();
        Object object =  dsm.toObject(xmlContent);

изменить файл сопоставления для вызова зарегистрированной функции

result:     
   type: object
   path: /root   
   fields:
     listOfA:
       type: object
       function: processA  # when 'class_a_object' tag closed processA function will be executed.
       path: .*class_a_object  # path is regex
       fields:
          fieldOfA:
            path: fieldA
     listOfB:
       type: object
       path: .*class_b_object
       function: processB# register function
       fields:
          fieldOfB:
            path: fieldB 
     listOfC:
       type: object
       path: .*class_c_object
       function: processC# register function
       fields:
          fieldOfC:
            path: fieldC 
person mfe    schedule 14.05.2019
comment
Большое спасибо за решение, но разве он не будет ждать, пока весь XML будет проанализирован для создания одного объекта. XML действительно очень большой, и мне было интересно, есть ли лучший способ проанализировать XML и начать обработку объектов до того, как будет достигнут конец файла. Кстати, знаете ли вы какую-нибудь библиотеку, которая конвертировала бы java POJO в YAML? - person Nick Div; 15.05.2019
comment
DSM позволяет обрабатывать проанализированные данные во время чтения. Вам не нужно ждать конца файла. Также DSM поддерживает сценарии и выражения, поэтому вы можете манипулировать данными и фильтровать их во время чтения файла. Вот пример использования DSM для большого XML " title="лучший подход java для анализа огромного очень большого файла json"> stackoverflow.com/questions/9390368/ SnakeYaml или Jackson - это библиотека для преобразования java в yaml. - person mfe; 15.05.2019
comment
Потрясающий. Позвольте мне попробовать это. - person Nick Div; 15.05.2019
comment
Я не могу анализировать встроенные переменные, например. ‹root generate_date =2019-01-01› Есть ли какое-то другое объявление в файле сопоставления для этого - person Nick Div; 15.05.2019
comment
mfatihercik.github.io/dsm/build/html/ спецификация/. атрибут: правда - person mfe; 15.05.2019

Вы можете использовать Архитектуру Java для XML-привязки JAXB и Unmarshall, используя классы POJO, как указано ниже.

Сначала создайте классы POJO (я взял несколько узлов из вашего XML-файла и создал POJO. Вы можете сделать то же самое для остальных). Ниже приведен XML, который я рассматривал.

<?xml version="1.0" encoding="utf-8"?>
<root>
    <category_alpha>
        <list_a>
            <class_a_object></class_a_object>
            <class_a_object></class_a_object>
            <class_a_object></class_a_object>
            <class_a_object></class_a_object>
        </list_a>
        <list_b>
            <class_b_object></class_b_object>
            <class_b_object></class_b_object>
            <class_b_object></class_b_object>
            <class_b_object></class_b_object>
        </list_b>
    </category_alpha>
</root>

Ниже приведены классы POJO для Root, category_alpha, list_a, list_b, class_a_object и class_b_object.

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;


@XmlRootElement(name = "root")
@XmlAccessorType (XmlAccessType.FIELD)
public class Root {

    @XmlElement(name = "category_alpha")
    private List<CategoryAlpha> categoryAlphaList = null;

    public List<CategoryAlpha> getCategoryAlphaList() {
        return categoryAlphaList;
    }

    public void setCategoryAlphaList(List<CategoryAlpha> categoryAlphaList) {
        this.categoryAlphaList = categoryAlphaList;
    }
}

Импортируйте аналогичный импорт java в приведенный выше класс здесь, в следующих классах.

@XmlRootElement(name = "category_alpha")
@XmlAccessorType (XmlAccessType.FIELD)
public class CategoryAlpha {

    @XmlElement(name = "list_a")
    private List<ListAClass> list_a_collectionlist = null;

    @XmlElement(name = "list_b")
    private List<ListBClass> list_b_collectionlist = null;


    public List<ListAClass> getList_a_collectionlist() {
        return list_a_collectionlist;
    }


    public void setList_a_collectionlist(List<ListAClass> list_a_collectionlist) {
        this.list_a_collectionlist = list_a_collectionlist;
    }


    public List<ListBClass> getList_b_collectionlist() {
        return list_b_collectionlist;
    }


    public void setList_b_collectionlist(List<ListBClass> list_b_collectionlist) {
        this.list_b_collectionlist = list_b_collectionlist;
    }
}

@XmlRootElement(name = "list_a")
@XmlAccessorType (XmlAccessType.FIELD)
public class ListAClass {

    @XmlElement(name = "class_a_object")
    private List<ClassAObject> classAObjectList = null;

    public List<ClassAObject> getClassAObjectList() {
        return classAObjectList;
    }

    public void setClassAObjectList(List<ClassAObject> classAObjectList) {
        this.classAObjectList = classAObjectList;
    }
}

@XmlRootElement(name = "list_b")
@XmlAccessorType (XmlAccessType.FIELD)
public class ListBClass {

    @XmlElement(name = "class_b_object")
    private List<ClassBObject> classBObjectList = null;

    public List<ClassBObject> getClassBObjectList() {
        return classBObjectList;
    }

    public void setClassBObjectList(List<ClassBObject> classBObjectList) {
        this.classBObjectList = classBObjectList;
    }
}

@XmlRootElement(name = "class_a_object")
@XmlAccessorType (XmlAccessType.FIELD)
public class ClassAObject {

}

@XmlRootElement(name = "class_b_object")
@XmlAccessorType (XmlAccessType.FIELD)
public class ClassBObject {

}

Вот основной класс

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

public class UnmarshallMainClass {

    public static void main(String[] args) throws JAXBException {
        JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();

        // This root object contains all the list of objects you are looking for
        Root emps = (Root) jaxbUnmarshaller.unmarshal( new File("sample.xml") );
    }

}

Используя геттеры в корневом объекте и других объектах, вы можете получить список всех объектов внутри корня, как показано ниже.

List<CategoryAlpha> categoryAlphaList = emps.getCategoryAlphaList();
person Subash J    schedule 19.05.2019
comment
Большое спасибо Субаш. Ваше решение определенно сработает, но я думаю, что буду придерживаться потокового синтаксического анализатора DSM, поскольку он позволяет мне манипулировать объектами до того, как будет достигнут конец XML. - person Nick Div; 21.05.2019
comment
Привет, спасибо за этот ответ. Я делаю что-то подобное, но не могу упорядочить несколько событий в своем классе. Если у вас есть возможность, не могли бы вы взглянуть на этот вопрос и поделиться своим наблюдением? stackoverflow.com/questions/67667516/ - person BATMAN_2008; 24.05.2021

Я создал парсер для предоставленного примера. https://github.com/sbzDev/stackoverflow/tree/master/question56087924

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.List;

@XStreamAlias("root")
public class Root {

    String notes;

    @XStreamAlias("category_alpha")
    CategoryAlpha categoryAlpha;


    @XStreamAlias("category_beta")
    List<C> listC;

    static class CategoryAlpha {

        @XStreamAlias("list_a")
        List<A> listA;

        @XStreamAlias("list_b")
        List<B> listB;
    }

    @XStreamAlias("class_a_object")
    static class A {
    }

    @XStreamAlias("class_b_object")
    static class B {
    }

    @XStreamAlias("class_c_object")
    static class C {
    }
}

Парсер:

import com.thoughtworks.xstream.XStream;

public class SampleRootParser {

    public Root parse(String xmlContent){
        XStream xstream = new XStream();
        xstream.processAnnotations(Root.class);
        return  (Root)xstream.fromXML(xmlContent);
    }
}

Может быть, вы можете предоставить фактический XML и ожидаемый результат?

person Sergey Bzhezitskiy    schedule 13.05.2019
comment
Это почти похоже на то, что я использую, к сожалению, и это совсем не эффективно, потому что мы анализируем весь XML за одно чтение, и XStream теряет здесь свое преимущество. - person Nick Div; 13.05.2019