Kotlin - анализ ответа Xml со списком с использованием SimpleXml

Я пытаюсь разобрать XML-ответ от API и столкнулся со списком элементов «Поле». Я пытаюсь создать объект 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
    )
}

person shadow762    schedule 27.05.2020    source источник


Ответы (1)


Я не знаком с этой библиотекой, но я пытался ее отладить и вот мои выводы.

Согласно документации, эта библиотека работает либо с использованием пустой конструктор с аннотацией поля/геттера/установщика или с помощью внедрения конструктора + аннотации геттера.

В первом случае вам нужно будет избавиться от вашего модификатора 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)])])))])

Как видите, это не идеально, но работает.

Вероятно, лучший подход будет включать использование внедрения конструктора, для которого также требуются аннотированные геттеры или поля (как указано в документации, указанной выше).

Для этого вам нужно использовать аннотации @param: (так как вам нужно аннотировать аргументы конструктора) плюс аннотации @get: (так как вам нужно аннотировать геттер). Пример:

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)])])))])
person user2340612    schedule 27.05.2020
comment
Большое спасибо за ответ. Я понял свою ошибку. - person shadow762; 28.05.2020