Элемент привязки XML Джексона, основанный на значении атрибутов - PullRequest
0 голосов
/ 05 апреля 2019

У меня есть следующая XML структура:

<participants>
    <participant side="AWAY">
        <team id="18591" name="Orlando Apollos" />
    </participant>
    <participant side="HOME">
        <team id="18594" name="Memphis Express" />
    </participant>
</participants>

Если я использую библиотеку FasterXML Jackson с аннотациями JAXB, как я могу связать поля участников с двумя различными Participant объектами participantHome и participantAway, используя свойство side AWAY и HOME для привязки полей.

Использование следующего объекта не будет работать, очевидно, из-за наличия дублирующих полей:

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "participants")
public class Participants {

    @XmlElement(name = "participant")
    Participant participantHome;

    @XmlElement(name = "participant")
    Participant participantAway;
}

Как я могу динамически связать эти элементы, используя аннотации JAXB или собственную реализацию JAXB?

Ответы [ 3 ]

1 голос
/ 05 апреля 2019

Вам необходимо написать собственный десериализатор, потому что нет аннотации, позволяющей привязать элемент списка к данному свойству в объекте.Если вы уже используете Jackson, попробуйте реализовать пользовательский JsonDeserializer вместо пользовательского XmlAdapter.Мы можем упростить наш пользовательский десериализатор, десериализовав внутренние Participant объекты до Map.Простой пример:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class XmlMapperApp {

    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();

        XmlMapper xmlMapper = new XmlMapper();

        Participants result = xmlMapper.readValue(xmlFile, Participants.class);
        System.out.println(result);
    }
}

class ParticipantsXmlAdapter extends JsonDeserializer<Participants> {

    @Override
    public Participants deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        List<Map<String, Object>> participants = readParticipantsMap(p, ctxt);

        Participants result = new Participants();
        for (Map<String, Object> participantMap : participants) {
            Object side = participantMap.get("side").toString();
            if ("AWAY".equals(side)) {
                result.setParticipantAway(convert((Map<String, Object>) participantMap.get("team")));
            } else if ("HOME".equals(side)) {
                result.setParticipantHome(convert((Map<String, Object>) participantMap.get("team")));
            }
        }

        return result;
    }

    private List<Map<String, Object>> readParticipantsMap(JsonParser p, DeserializationContext ctxt) throws IOException {
        MapType mapType = ctxt.getTypeFactory().constructMapType(Map.class, String.class, Object.class);
        JsonDeserializer<Object> mapDeserializer = ctxt.findRootValueDeserializer(mapType);
        List<Map<String, Object>> participants = new ArrayList<>();
        p.nextToken(); // skip Start of Participants object
        while (p.currentToken() == JsonToken.FIELD_NAME) {
            p.nextToken(); // skip start of Participant
            Object participant = mapDeserializer.deserialize(p, ctxt);
            participants.add((Map<String, Object>) participant);
            p.nextToken(); // skip end of Participant
        }

        return participants;
    }

    private Participant convert(Map<String, Object> map) {
        Participant participant = new Participant();
        participant.setId(Integer.parseInt(map.get("id").toString()));
        participant.setName(map.get("name").toString());

        return participant;
    }
}

@JsonDeserialize(using = ParticipantsXmlAdapter.class)
class Participants {

    private Participant participantHome;
    private Participant participantAway;

    // getters, setters, toString
}

class Participant {
    private int id;
    private String name;

    // getters, setters, toString
}

отпечатки:

Participants{participantHome=Participant{id=18594, name='Memphis Express'}, participantAway=Participant{id=18591, name='Orlando Apollos'}}
1 голос
/ 05 апреля 2019

Вы можете использовать Список участников вместо двух разных участников.Аннотируйте side с помощью @ XmlAttribute (name = "side", обязательный = true) .Затем создайте два разных объекта Участника и добавьте их в список.

0 голосов
/ 05 апреля 2019

Несколько отличных ответов и альтернатив здесь, но я решил использовать гибрид связывания со списком и вернуть правильную команду home или away, реализовав методы получения, которые возвращают правильную домашнюю или выездную команду в существенно сгладить List. Это уменьшит объем вычислений при обработке списков по всему приложению.

Я добавил следующий код в родительский класс (для каждого home / away участника):

Participant getHome() {
    return (Participant) participants.stream()
            .filter(p -> p.getSide().equalsIgnoreCase("home"));
}

Participant getAway() {
    return (Participant) participants.stream()
            .filter(p -> p.getSide().equalsIgnoreCase("away"));
}

Спасибо за помощь!

...