Phần tử gốc động JAXB?


8

Tôi đang cố gắng tích hợp với hệ thống của bên thứ ba và tùy thuộc vào loại đối tượng, phần tử gốc của tài liệu XML được trả về sẽ thay đổi. Tôi đang sử dụng thư viện JAXB cho Marshalling / unmarshalling.

Root1:

<?xml version="1.0" encoding="UTF-8"?>
<root1 id='1'>
   <MOBILE>9831138683</MOBILE>
   <A>1</A>
   <B>2</B>
</root1>

Root2:

<?xml version="1.0" encoding="UTF-8"?>
<root2 id='3'>
   <MOBILE>9831138683</MOBILE>
   <specific-attr1>1</specific-attr1>
   <specific-attr2>2</specific-attr2>
</root2>

Tôi đang tiêu thụ tất cả các XML khác nhau ánh xạ chúng vào một đối tượng chung :

 @XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "ROW")
public class Row {

    @XmlAttribute
    private int id;
    @XmlElement(name = "MOBILE")
    private int mobileNo;

    @XmlMixed
    @XmlAnyElement
    @XmlJavaTypeAdapter(MyMapAdapter.class)
    private Map<String, String> otherElements;
}

bộ chuyển đổi để biến các giá trị uknown thành bản đồ :

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.HashMap;
import java.util.Map;

public class MyMapAdapter extends XmlAdapter<Element, Map<String, String>> {

    private Map<String, String> hashMap = new HashMap<>();

    @Override
    public Element marshal(Map<String, String> map) throws Exception {
        // expensive, but keeps the example simpler
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

        Element root = document.createElement("dynamic-elements");

        for(Map.Entry<String, String> entry : map.entrySet()) {
            Element element = document.createElement(entry.getKey());
            element.setTextContent(entry.getValue());
            root.appendChild(element);

        }

        return root;
    }


    @Override
    public Map<String, String> unmarshal(Element element) {
        String tagName = element.getTagName();
        String elementValue = element.getChildNodes().item(0).getNodeValue();
        hashMap.put(tagName, elementValue);

        return hashMap;
    }
}

Điều này sẽ đưa id và số điện thoại di động vào các trường và phần còn lại, không xác định vào bản đồ .

Điều này hoạt động nếu Phần tử gốc được cố định vào ROW như trong ví dụ trên.

Làm thế nào để làm cho công việc này sao cho phần tử gốc sẽ khác nhau trong mỗi XML? Một cách để có thể chỉ là bất khả tri đối với phần tử gốc trong khi không sắp xếp?


1
Tôi nghĩ là chung chung vượt quá tất cả sử dụng. Không có hợp đồng ở đây. Bạn có thể trả lại bất cứ điều gì bạn muốn. Bạn làm cho người dùng của bạn đoán. Tôi sẽ tạo một API tốt hơn có các phương thức rõ ràng cho từng loại được trả về hoặc bỏ yêu cầu này.
duffymo

Atleast bạn có biết tất cả các yếu tố gốc có thể là gì?
Harshal Khachane

@HarshalKhachane Có tôi biết bộ!
Siddharth Trikha

Tập hợp này không quá dài, sau đó bạn có thể chỉ cần sử dụng tính kế thừa và di chuyển tất cả các XMLElements trong lớp cha và tạo các lớp con cho mỗi gốc.
Harshal Khachane

Câu trả lời:


3

Hãy quên JAXB về điều này và tự phân tích nó với StAX.

Trong mã bên dưới, tôi đã thay đổi trường mobileNotừ intthành String, vì giá trị 9831138683quá lớn cho một int.

private static Row parse(String xml) throws XMLStreamException {
    XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
    XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(new StringReader(xml));
    reader.nextTag(); // read root element
    Row row = new Row(Integer.parseInt(reader.getAttributeValue(null, "id")));
    while (reader.nextTag() == XMLStreamConstants.START_ELEMENT) {
        String tagName = reader.getLocalName();
        if (tagName.equals("MOBILE")) {
            row.setMobileNo(reader.getElementText());
        } else {
            row.addOtherElement(tagName, reader.getElementText());
        }
    }
    return row;
}
public class Row {
    private int id;
    private String mobileNo;
    private Map<String, String> otherElements = new LinkedHashMap<>();

    public Row(int id) {
        this.id = id;
    }
    public void setMobileNo(String mobileNo) {
        this.mobileNo = mobileNo;
    }
    public void addOtherElement(String name, String value) {
        this.otherElements.put(name, value);
    }

    // getters here

    @Override
    public String toString() {
        return "Row[id=" + this.id + ", mobileNo=" + this.mobileNo +
                 ", otherElements=" + this.otherElements + "]";
    }
}

Kiểm tra

public static void main(String[] args) throws Exception {
    test("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
         "<root1 id='1'>\n" +
         "   <MOBILE>9831138683</MOBILE>\n" +
         "   <A>1</A>\n" +
         "   <B>2</B>\n" +
         "</root1>");
    test("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
         "<root2 id='3'>\n" +
         "   <MOBILE>9831138683</MOBILE>\n" +
         "   <specific-attr1>1</specific-attr1>\n" +
         "   <specific-attr2>2</specific-attr2>\n" +
         "</root2>");
}
private static void test(String xml) throws XMLStreamException {
    System.out.println(parse(xml));
}

Đầu ra

Row[id=1, mobileNo=9831138683, otherElements={A=1, B=2}]
Row[id=3, mobileNo=9831138683, otherElements={specific-attr1=1, specific-attr2=2}]

Tôi không chắc chắn về việc liệu làm việc ở mức độ trừu tượng thấp hơn (thông qua StAX) cho loại này xmlso với việc không lưu trữ thông qua JAXB (đối với các đối tượng dữ liệu đã biết nơi xác định XSD), sẽ tốt hơn. Ngay cả khi tôi sử dụng StAX, tôi cần tuần tự hóa lại Rowđối tượng thành xml thích hợp với phần tử gốc phù hợp.
Siddharth Trikha

Ngoài ra, tôi phải chăm sóc cả hai đầu vào XML và JSON.
Siddharth Trikha

1
@SiddharthTrikha Nếu bạn cần giữ lại tên thành phần gốc khi tuần tự hóa trở lại XML, bạn cần phải có Rowlớp nhớ tên. --- Nếu bạn cần chăm sóc đầu vào JSON, hãy sử dụng trình phân tích cú pháp JSON.
Andreas

1
@Andreas điểm tuyệt vời khi tạo mobileNoChuỗi. Ngay cả khi intđủ lớn để lưu trữ số điện thoại, giá trị không đại diện cho một số tiền. IMO, điều này đảm bảo nó được coi là một chuỗi ngay cả khi giá trị là số.
hfontanez

1

Tôi không nghĩ có một cách để làm những gì bạn đang yêu cầu. Trong XML, nút gốc (tài liệu) phải có một phần tử (hoặc lớp) được xác định. Nói cách khác, xs:anychỉ hoạt động cho các yếu tố phụ. Ngay cả khi có một cách để đạt được điều này, IMHO đây là một quyết định tồi. Thay vì tạo một phần tử gốc biến "(động"), bạn nên thêm một thuộc tính tên cho cùng một phần tử để phân biệt các tệp XML. Ví dụ:

<?xml version="1.0" encoding="UTF-8"?>
<ROW id='1' name="me">
   <MOBILE>9831138683</MOBILE>
   <specific-attr1>1</specific-attr1>
   <specific-attr2>2</specific-attr2>
</ROW>


<?xml version="1.0" encoding="UTF-8"?>
<ROW id='2' name="you">
   <MOBILE>123456790</MOBILE>
   <specific-attr1>3</specific-attr1>
   <specific-attr2>4</specific-attr2>
</ROW>

Đối với điều này, tất cả những gì bạn cần làm là thêm một thuộc tính tên cho thành phần hiện có của bạn:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "ROW")
public class Row {

    @XmlAttribute
    private int id;

    @XmlAttribute(name = "name", required=true)
    private String name;

    @XmlElement(name = "MOBILE")
    private int mobileNo;

    @XmlMixed
    @XmlAnyElement
    @XmlJavaTypeAdapter(MyMapAdapter.class)
    private Map<String, String> otherElements;
}

Tôi đang tiêu thụ XML từ một khách hàng, vì vậy không thể thay đổi cấu trúc XML. Tôi đã cố gắng loại bỏ XmlRootElementtừ Rowvà unmarshalling để JAXBElementtheo: JAXBElement<Row> element = unmarshaller.unmarshal(new StreamSource(xml), Row.class); Row root = element.getValue();. Điều này mang lại sự phổ biến của tất cả các trường và tiêu thụ XML với các phần tử gốc khác nhau.
Siddharth Trikha

@SiddharthTrikha trong trường hợp đó, bạn cần nhiều lớp cơ sở để thể hiện phần tử gốc của mình, đây sẽ là một mớ hỗn độn và rất có thể sẽ không mở rộng tốt. Nút gốc của bạn không thể xs:any. Tôi cũng đã thử điều này với lược đồ XML. Một bạn đặt phần tử gốc của bạn về cơ bản là bất cứ thứ gì, lược đồ của bạn bị phá vỡ. Nói cách khác, xs:anychỉ có thể được sử dụng trong ngữ cảnh của một nút con. Cuối cùng, bạn cần phải đến khách hàng của mình và cho họ thấy lý do tại sao đây là một triển khai tồi.
hfontanez

@SiddharthTrikha Tôi hiểu rằng việc xóa XmlRootElementkhỏi Rowsẽ hoạt động khi không sắp xếp. Câu hỏi của tôi cho bạn là, bạn sẽ làm thế nào nếu bạn phải? Bạn có thể làm điều đó với các cơ chế khác, nhưng không phải với JAXB. Nhược điểm của phương pháp này là sau đó bạn sẽ cần phải viết trình xác nhận của riêng bạn. Có thể làm được, nhưng bạn mất khả năng riêng của JAXB để làm điều đó.
hfontanez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.