動的ルート要素JAXB?


8

サードパーティのシステムと統合しようとしていますが、オブジェクトのタイプによっては、返されるXMLドキュメントのルート要素が変化します。マーシャリング/アンマーシャリングにJAXBライブラリを使用しています。

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>

私はそれらを汎用オブジェクトにマッピングするさまざまなXMLをすべて使用しています

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

そして、不明な値をマップに変換するためアダプター

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

これにより、フィールドIDと携帯電話番号が表示され、残りは不明なものが地図に表示されます

これは、上記の例のようにルート要素がROWに固定されている場合に機能します。

ルート要素が各XML で異なるようにこれを機能させるにはどうすればよいですか?非整列化中にルート要素にとらわれない方法はありますか?


1
私はすべての使用を超えて一般的だと思います。ここでは契約はありません。何でも返品できます。ユーザーに推測させます。私は、返されるタイプごとに明示的なメソッドを持つより優れたAPIを作成するか、この要件を削除します。
duffymo

少なくとも、すべての可能なルート要素を知っていますか?
Harshal Khachane

@HarshalKhachaneはい、私はセットを知っています!
Siddharth Trikha

セットが長すぎない場合は、継承を使用して、すべてのXMLElementを親クラスに移動し、すべてのルートに子クラスを作成できます。
Harshal Khachane

回答:


3

このためのJAXBを忘れて、StAXで自分で解析してください。

次のコードでは、値がには大きすぎるため、フィールドをmobileNoからintに変更しました。String9831138683int

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 + "]";
    }
}

テスト

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

出力

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

このタイプの抽象化レベル(StAX経由)とxmlJAXBによる非整列化(XSDが定義されている既知のデータオブジェクトの場合)の抽象化のレベルが低いかどうかはわかりません。StAXを使用している場合でもRow、一致するルート要素を持つ適切なxmlにオブジェクトをシリアル化する必要があります。
Siddharth Trikha

さらに、XML入力とJSON入力の両方を処理する必要があります。
Siddharth Trikha

1
@SiddharthTrikha XMLにシリアル化するときにルート要素名を保持する必要がある場合は、Rowクラスに名前を記憶させる必要があります。--- JSON入力を処理する必要がある場合は、JSONパーサーを使用します。
Andreas

1
@AndreasのmobileNoストリング作成の優れた点。int電話番号を格納するのに十分な大きさであったとしても、値は金額を表していません。IMO、これは値が数値であっても文字列として扱われることを保証します。
hfontanez

1

私はあなたが求めていることをする方法はないと思います。XMLでは、ルートノード(ドキュメント)に定義済みの要素(またはクラス)が必要です。つまりxs:any、サブ要素に対してのみ機能します。これを達成する方法があったとしても、これは悪い決断です。変数(「動的」)ルート要素を作成する代わりに、同じ要素に名前属性を追加して、XMLファイルを区別する必要があります。例えば:

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

このために必要なことは、name属性を既存の要素に追加することだけです。

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

クライアントからXMLを使用しているため、XML構造を変更できません。XmlRootElementから削除しRowて非整列化を試みましJAXBElementた:JAXBElement<Row> element = unmarshaller.unmarshal(new StreamSource(xml), Row.class); Row root = element.getValue();。これにより、すべてのフィールドにデータが入力され、異なるルート要素を持つXMLが消費されます。
Siddharth Trikha

その場合の@SiddharthTrikhaでは、ルート要素を表すために複数の基本クラスが必要です。ルート要素はごちゃごちゃしていて、うまく拡張できない可能性があります。ルートノードをにすることはできませんxs:any。XMLスキーマでもこれを試しました。ルート要素を基本的に何かに設定すると、スキーマが壊れます。つまり、xs:any子ノードのコンテキストでのみ使用できます。最後に、クライアントに行って、これが悪い実装である理由をクライアントに示す必要があります。
hfontanez

@SiddharthTrikha私は、マーシャリングを解除するときにXmlRootElementからの削除Rowが機能することを理解しています。あなたへの私の質問は、もし必要ならどうやってそれを整理するかということです。他のメカニズムでも可能ですが、JAXBではできません。このアプローチの欠点は、独自のバリデーターを作成する必要があることです。可能ですが、そうするためにJAXBのネイティブ機能が失われます。
hfontanez
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.