Home > Software design >  Unmarshal xml property with both attribute values and subelements
Unmarshal xml property with both attribute values and subelements

Time:10-30

I'm using Jackson (jackson-dataformat-xml, v2.13.0) to map an XML string into a corresponding Kotlin object. The XML looks like this:

<tide>
    <service cominfo="Some text..."/>
    <locationdata>
        <location name="Oslo" code="OSL" latitude="59.904794"
              longitude="10.753642" delay="0" factor="1.00" obsname="Oslo"
              obscode="OSL" descr="Tidevann fra Oslo"/>
        <reflevelcode>CD</reflevelcode>
        <data type="prediction" unit="cm">
            <waterlevel value="88.5" time="2021-10-22T06:50:00 01:00" flag="high"/>
            <waterlevel value="69.8" time="2021-10-22T11:53:00 01:00" flag="low"/>
            <waterlevel value="95.1" time="2021-10-22T19:05:00 01:00" flag="high"/>
        </data>
    </locationdata>
</tide>

I'm having a hard time extracting whatever is inside the data field, because it contains both attribute values (type and unit) as well as a list of waterlevel.

In my controller, I'm doing a simple mapping:

val mapper: ObjectMapper = XmlMapper.builder()
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)
            .build()
val tide: Tide = mapper.readValue(tideLevels, Tide::class.java)

I am unable to get everything from the data tag. It seems to me as if I have to choose between attributes and sub elements. The following mapping:

data class Tide(
    val service: TideServiceInfo? = null,
    val locationdata: LocationData? = null
)

data class LocationData(
    val location: Location? = null,
    val reflevelcode: String = "",
    val data: TideData? = null
)

data class TideData(
    val type: String = "",
    val unit: String = "",
    val waterlevel: List<WaterLevel> = emptyList()
)

data class WaterLevel(
    val value: String = "",
    val time: String = "",
    val flag: String = ""
)

Gives me resulting json with an empty waterlevel-array:

{
    "service": {
        "cominfo": "Some text..."
    },
    "locationdata": {
        "location": {shortened for brevity},
        "reflevelcode": "CD",
        "data": {
            "type": "prediction",
            "unit": "cm",
            "waterlevel": []
        }
    }
}

But if I instead choose to map to the following data classes:

data class Tide(
    val service: TideServiceInfo? = null,
    val locationdata: LocationData? = null
)

data class LocationData(
    val location: Location? = null,
    val reflevelcode: String = "",
    val data: List<WaterLevel> = emptyList()
)

data class WaterLevel(
    val value: String = "",
    val time: String = "",
    val flag: String = ""
)

My result json has the data field set, without type and unit:

{
    "service": {
        "cominfo": "Some text..."
    },
    "locationdata": {
        "location": { shortened for brevity },
        "reflevelcode": "CD",
        "data": [
            {
                "value": "88.5",
                "time": "2021-10-22T06:50:00 01:00",
                "flag": "high"
            },
            {
                "value": "69.8",
                "time": "2021-10-22T11:53:00 01:00",
                "flag": "low"
            },
            {
                "value": "95.1",
                "time": "2021-10-22T19:05:00 01:00",
                "flag": "high"
            }
        ]
    }
}

I have tried looking into annotations to put on data fields, e.g. @JacksonXmlProperty, but I don't see how that can help me achieve my solution. Am I missing something obvious here?

CodePudding user response:

Add

@field:JacksonXmlProperty(isAttribute = true)

to your attribute fields. See the docs for more info.

Note that you need @field in Kotlin to apply the annotation to the actual object field

CodePudding user response:

The suggestions from @Evgeny did not work for me, but it did lead me on the right path searching on. Basically, the answer suggested here, introducing the useWrapping=false is what turned out to do the trick.

So I got what I wanted using the following:

@field:JacksonXmlElementWrapper(useWrapping = false)
    val waterlevel: List<WaterLevel> = emptyList()
  • Related