【问题标题】:Jackson XML Bind Element based on value of attributes基于属性值的 Jackson XML 绑定元素
【发布时间】:2019-08-26 18:06:00
【问题描述】:

我有以下XML 结构:

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

如果我使用带有JAXB 注释的FasterXML Jackson 库,如何使用AWAY 和@ 的side 属性将参与者字段绑定到两个不同的Participant 对象participantHome 和participantAway 987654330@绑定字段。

使用以下对象显然不起作用,因为存在重复的字段:

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 实现动态绑定这些元素?

【问题讨论】:

  • Participant 类里面有什么?
  • Participant 类只包含 side 属性 - 里面有一个 Team 属性对象。映射符合预期。我最常遇到的问题是使用side=AWAY 或side=HOME 属性值映射相同的参与者元素
  • 在我的实际真实代码中,我收到以下错误:com.fasterxml.jackson.databind.JsonMappingException: Multiple fields representing property "participant":

标签: java xml jackson jaxb xml-deserialization


【解决方案1】:

这里有几个很好的答案和替代方案,但我决定使用与列表绑定并返回正确的 home 或 away 团队的混合方法,方法是实现返回正确的主队或客队的 getter 方法基本上压平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"));
}

感谢您的帮助!

【讨论】:

    【解决方案2】:

    您需要编写自定义反序列化器,因为没有允许将列表项绑定到对象中的给定属性的注释。如果您已经使用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'}}
    

    【讨论】:

    • 很好的答案,谢谢。非常有帮助。我选择绑定到一个列表并添加一个 getter 方法来过滤主/客队。主要原因是因为我的实际示例在父级上更复杂,我不想添加太多自定义复杂性。但是,您的答案最接近我展示的案例和我想要的答案。
    【解决方案3】:

    您可以使用参与者列表代替两个不同的参与者。 用 @XmlAttribute(name = "side", required = true) 注释 side。 然后创建两个不同的 Participant 对象并将它们添加到列表中。

    【讨论】:

    • 感谢您的回答。我选择了您的答案,因为我的父 xml 元素比上面的简化示例更复杂,而且我觉得另一个答案(对于这个简单示例来说是完美的)对于我的真实示例来说太多了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多