Kotlin - 使用 SimpleXml 解析带有列表的 Xml 响应

Kotlin - parsing Xml response with list using SimpleXml

我正在尝试解析来自 API 的 XML 响应,但 "Field" 元素列表有问题。我正在尝试使用元素内的属性和文本创建 Field 对象,例如此对象的属性,但找不到我需要为元素文本使用的注释。 我尝试使用

  @get:Text
  @set:Text

@field:Text

但得到同样的错误

java.lang.RuntimeException: org.simpleframework.xml.core.PersistenceException: Constructor not matched for class NetworkTestContainer$Field

任何人都可以为这种情况建议一个合适的注释吗?

有XML个结构:

<response>
<commands>
    <command>
        <nick>QUEUECAUSES_LIST</nick>
        <result>
            <DATASET Version="1.0" Class="TQueryAdv" Name="">
                <Row Index="1">
                    <Field Name="SHOPID" Type="6" Size="0">-1</Field>
                    <Field Name="IDCODE" Type="6" Size="0">3000000000001</Field>
                    <Field Name="CAPTION" Type="1" Size="255">Консультация</Field>
                    <Field Name="PRIORITY" Type="6" Size="0">0</Field>
                </Row>
                <Row Index="2">
                    <Field Name="SHOPID" Type="6" Size="0">-1</Field>
                    <Field Name="IDCODE" Type="6" Size="0">3000000000021</Field>
                    <Field Name="CAPTION" Type="1" Size="255">Очередь</Field>
                    <Field Name="PRIORITY" Type="6" Size="0">1</Field>
                </Row>
            </DATASET>
        </result>
    </command>
</commands>
</response>

这是模特

@Root(name = "response", strict = false)
data class NetworkTestContainer(
    @field:ElementList(name = "commands")
    val commands : List<Command>
) {
    @Root(name = "command", strict = false)
    data class Command(
        @field:Element
        val nick : String,
        @field:Element(name = "result")
        val result : Result
    )

    @Root(name = "result", strict = false)
    data class Result(
        @field:Element(name = "DATASET")
        val dataSet: DataSet
    )

    @Root(name = "DATASET", strict = false)
    data class DataSet(
        @field:Attribute(name = "Version")
        val version : String,

        @field:Attribute(name = "Class")
        val className : String,

        @field:Attribute(name = "Name")
        val Name : String,

        @field:ElementList(inline = true)
        val rows : List<Row>
    )

    @Root(name = "Row", strict = false)
    data class Row(
        @field:Attribute(name = "Index")
        val index : Int,

        @field:ElementList(inline = true, entry = "Field")
        val fields: List<Field>

//        @field:ElementMap(attribute = true, entry = "Field", key = "Name", inline = true)
//        val fields : Map<String, String>
    )

    @Root(name = "Field", strict = false)
    data class Field (
        @field:Attribute(name = "Name", required = false)
        val key : String,

        @field:Attribute(name = "Type", required = false)
        val type : Int,

        @field:Attribute(name = "Size", required = false)
        val size : Int,

        @get:Text
        @set:Text
        var text : String
    )
}

我不熟悉这个库,但我尝试调试它,这是我的发现。

根据 the documentation,该库通过使用带 field/getter/setter 注释的空构造函数或使用构造函数注入 + getter 注释来工作。

在第一种情况下,您需要摆脱 data 修饰符并使用 @field: 注释,加上 lateinit var 或其他方法在创建对象时初始化所有字段。示例:

import org.simpleframework.xml.Attribute
import org.simpleframework.xml.Element
import org.simpleframework.xml.ElementList
import org.simpleframework.xml.Root
import org.simpleframework.xml.core.Persister


@Root(name = "response", strict = false)
class NetworkTestContainer {
    @field:ElementList(name = "commands")
    lateinit var commands: List<Command>
    override fun toString(): String {
        return "NetworkTestContainer(commands=$commands)"
    }

}

@Root(name = "command", strict = false)
class Command {
    @field:Element(name = "nick")
    lateinit var nick: String

    @field:Element(name = "result")
    lateinit var result: Result
    override fun toString(): String {
        return "Command(nick='$nick', result=$result)"
    }


}

@Root(name = "result", strict = false)
class Result {
    @field:Element(name = "DATASET")
    lateinit var dataSet: DataSet
    override fun toString(): String {
        return "Result(dataSet=$dataSet)"
    }


}

@Root(name = "DATASET", strict = false)
class DataSet {
    @field:Attribute(name = "Version")
    lateinit var version: String

    @field:Attribute(name = "Class")
    lateinit var className: String

    @field:Attribute(name = "Name")
    lateinit var name: String

    @field:ElementList(inline = true)
    lateinit var rows: List<Row>
    override fun toString(): String {
        return "DataSet(version='$version', className='$className', name='$name', rows=$rows)"
    }


}

@Root(name = "Row", strict = false)
class Row {
    @field:Attribute(name = "Index")
    var index: Int = 0

    @field:ElementList(inline = true, entry = "Field")
    lateinit var fields: List<Field>
    override fun toString(): String {
        return "Row(index=$index, fields=$fields)"
    }

//        @field:ElementMap(attribute = true, entry = "Field", key = "Name", inline = true)
//        val fields : Map<String, String>


}

@Root(name = "Field", strict = false)
class Field {
    @field:Attribute(name = "Name", required = false)
    lateinit var key: String

    @field:Attribute(name = "Type", required = false)
    var type: Int = 0

    @field:Attribute(name = "Size", required = false)
    var size: Int = 0
    override fun toString(): String {
        return "Field(key='$key', type=$type, size=$size)"
    }


}


object Main {

    @JvmStatic
    fun main(args: Array<String>) {
        val deserializer = Persister()
        val file = Main::class.java.getResourceAsStream("input.xml")
        val container = deserializer.read(NetworkTestContainer::class.java, file)
        println(container)
    }
}

这会打印:

NetworkTestContainer(commands=[Command(nick='QUEUECAUSES_LIST', result=Result(dataSet=DataSet(version='1.0', className='TQueryAdv', name='', rows=[Row(index=1, fields=[Field(key='SHOPID', type=6, size=0), Field(key='IDCODE', type=6, size=0), Field(key='CAPTION', type=1, size=255), Field(key='PRIORITY', type=6, size=0)]), Row(index=2, fields=[Field(key='SHOPID', type=6, size=0), Field(key='IDCODE', type=6, size=0), Field(key='CAPTION', type=1, size=255), Field(key='PRIORITY', type=6, size=0)])])))])

如您所见,这并不理想,但它确实有效。

一种 - 可能 - 更好的方法是使用构造函数注入,这也需要带注释的 getter 或字段(如上面链接的文档中指定)。

为此,您需要使用 @param: 注释(因为您需要注释构造函数参数)加上 @get: 注释(因为您需要注释 getter)。示例:

import org.simpleframework.xml.Attribute
import org.simpleframework.xml.Element
import org.simpleframework.xml.ElementList
import org.simpleframework.xml.Root
import org.simpleframework.xml.Text
import org.simpleframework.xml.core.Persister


@Root(name = "response", strict = false)
data class NetworkTestContainer(
        @param:ElementList(name = "commands")
        @get:ElementList(name = "commands")
        val commands: List<Command>
) {
    @Root(name = "command", strict = false)
    data class Command(
            @param:Element(name="nick")
            @get:Element(name="nick")
            val nick: String,
            @param:Element(name = "result")
            @get:Element(name = "result")
            val result: Result
    )

    @Root(name = "result", strict = false)
    data class Result(
            @param:Element(name = "DATASET")
            @get:Element(name = "DATASET")
            val dataSet: DataSet
    )

    @Root(name = "DATASET", strict = false)
    data class DataSet(
            @param:Attribute(name = "Version")
            @get:Attribute(name = "Version")
            val version: String,

            @param:Attribute(name = "Class")
            @get:Attribute(name = "Class")
            val className: String,

            @param:Attribute(name = "Name")
            @get:Attribute(name = "Name")
            val Name: String,

            @param:ElementList(inline = true)
            @get:ElementList(inline = true)
            val rows: List<Row>
    )

    @Root(name = "Row", strict = false)
    data class Row(
            @param:Attribute(name = "Index")
            @get:Attribute(name = "Index")
            val index: Int,

            @param:ElementList(inline = true, entry = "Field")
            @get:ElementList(inline = true, entry = "Field")
            val fields: List<Field>

//        @field:ElementMap(attribute = true, entry = "Field", key = "Name", inline = true)
//        val fields : Map<String, String>
    )

    @Root(name = "Field", strict = false)
    data class Field(
            @param:Attribute(name = "Name", required = false)
            @get:Attribute(name = "Name", required = false)
            val key: String,

            @param:Attribute(name = "Type", required = false)
            @get:Attribute(name = "Type", required = false)
            val type: Int,

            @param:Attribute(name = "Size", required = false)
            @get:Attribute(name = "Size", required = false)
            val size: Int
    )
}

object Main {

    @JvmStatic
    fun main(args: Array<String>) {
        val deserializer = Persister()
        val file = Main::class.java.getResourceAsStream("input.xml")
        val container = deserializer.read(NetworkTestContainer::class.java, file)
        println(container)
    }
}

这会打印与上面相同的内容(除了字符串的一些样式差异之外):

NetworkTestContainer(commands=[Command(nick=QUEUECAUSES_LIST, result=Result(dataSet=DataSet(version=1.0, className=TQueryAdv, Name=, rows=[Row(index=1, fields=[Field(key=SHOPID, type=6, size=0), Field(key=IDCODE, type=6, size=0), Field(key=CAPTION, type=1, size=255), Field(key=PRIORITY, type=6, size=0)]), Row(index=2, fields=[Field(key=SHOPID, type=6, size=0), Field(key=IDCODE, type=6, size=0), Field(key=CAPTION, type=1, size=255), Field(key=PRIORITY, type=6, size=0)])])))])