如何将对象和XML进行相互转换

125 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

前言

平时咱们进行开发项目的时候,将对象和XML文件进行相互转换的场景应该不少吧,而如果我们手写的话还是比较麻烦的,属性对应上节点,还要注意XML的格式。

今天就来介绍一个JDK自带的工具包:javax.xml.bind

定义对象

1669204155068.jpg

如果如图这样的xml我们如何转换成对象呢。

可以看到xml中有四个节点

  • TerminologyRequest
  • Terminology
  • TerminologyItemList
  • TerminologyItem

虽然是四个节点,但TerminologyItemList这个是TerminologyItem的集合,所以实际上只对应我们三个对象。

然后我们就直接根据节点的属性定义对应的对象就好了。

@Data
@XmlRootElement(name = "TerminologyRequest")
public class TerminologyRequest implements Serializable {
    private String senderid;
    private String msgid;
    private Terminology terminology;

    public String getSenderid() {
        return senderid;
    }

    public void setSenderid(String senderid) {
        this.senderid = senderid;
    }

    public String getMsgid() {
        return msgid;
    }

    public void setMsgid(String msgid) {
        this.msgid = msgid;
    }

    @XmlElement(name = "Terminology")
    public Terminology getTerminology() {
        return terminology;
    }

    public void setTerminology(Terminology terminology) {
        this.terminology = terminology;
    }
}
@Data
public class Terminology implements Serializable {

    private String valuecode;
    private String valuename;
    private String statuscode;
    private String versioncode;
    private String versionname;
    private String authorcode;
    private String authorname;
    private List<TerminologyItem> TerminologyItemList;

    public String getValuecode() {
        return valuecode;
    }

    public void setValuecode(String valuecode) {
        this.valuecode = valuecode;
    }

    public String getValuename() {
        return valuename;
    }

    public void setValuename(String valuename) {
        this.valuename = valuename;
    }

    public String getStatuscode() {
        return statuscode;
    }

    public void setStatuscode(String statuscode) {
        this.statuscode = statuscode;
    }

    public String getVersioncode() {
        return versioncode;
    }

    public void setVersioncode(String versioncode) {
        this.versioncode = versioncode;
    }

    public String getVersionname() {
        return versionname;
    }

    public void setVersionname(String versionname) {
        this.versionname = versionname;
    }

    public String getAuthorcode() {
        return authorcode;
    }

    public void setAuthorcode(String authorcode) {
        this.authorcode = authorcode;
    }

    public String getAuthorname() {
        return authorname;
    }

    public void setAuthorname(String authorname) {
        this.authorname = authorname;
    }

    @XmlElementWrapper(name = "TerminologyItemList")
    @XmlElement(name = "TerminologyItem")
    public List<TerminologyItem> getTerminologyItemList() {
        return TerminologyItemList;
    }

    public void setTerminologyItemList(List<TerminologyItem> terminologyItemList) {
        TerminologyItemList = terminologyItemList;
    }
}
@Data
public class TerminologyItem implements Serializable {

    private String itemcode;
    private String itemname;
    private String itemstatuscode;

    public String getItemcode() {
        return itemcode;
    }

    public void setItemcode(String itemcode) {
        this.itemcode = itemcode;
    }

    public String getItemname() {
        return itemname;
    }

    public void setItemname(String itemname) {
        this.itemname = itemname;
    }

    public String getItemstatuscode() {
        return itemstatuscode;
    }

    public void setItemstatuscode(String itemstatuscode) {
        this.itemstatuscode = itemstatuscode;
    }
}
  • 首先我们需要对根节点对应的对象类上加上@XmlRootElement注解并注明对应的节点名,请参考TerminologyRequest对象
  • 然后对于自己的子节点对应的对象可以不加@XmlRootElement但是需要在get方法上加上@XmlElement注解并注明对应的子节点名,请参考TerminologyRequest对象的getTerminology()方法
  • 如果有个节点对应的并不是一个对象,他的节点下一个节点才是对象,比如集合。这时候需要在get方法上加上两个注解:@XmlElementWrapper表示集合对应的节点,@XmlElement表示集合内部的对象,请参考Terminology对象的getTerminologyItemList()方法

转换

接着我们使用JAXBContext就可以进行对象和XML的相互转换了。

public static String beanToXml(Object obj, Class c) {

    if (obj == null){
        return null;
    }
    String xml = null;
    try {
        JAXBContext context = JAXBContext.newInstance(c);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
        Writer w = new StringWriter();
        marshaller.marshal(obj, w);
        xml = w.toString();
    } catch (Exception e) {
        System.out.println(e);
    }

    return xml;
}

public static <T> T xmlToBean(String xml, Class<T> c) {

    if (StringUtils.isEmpty(xml)){
        return null;
    }
    T t = null;
    try {
        JAXBContext context = JAXBContext.newInstance(c);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        t = (T) unmarshaller.unmarshal(new StringReader(xml));
    } catch (Exception e) {
        System.out.println(e);
    }

    return t;
}

包含null属性的转换

实际上,上面的转换会丢失掉一些节点属性,当对象属性为空的时候,转换成XML并不会显示出为空的属性。

针对这种情况,我们怎么办呢?

这就需要介绍到Marshaller中的一个内部类Listener了。

他实际上就是一个回调机制,定义了对象转换对象之前和之后的回调。

所以我们在转换之前就可以提前将空值的属性赋一个默认值,这样就能实现XML所需的属性了。

  • 首先定义一个类继承Marshaller.Listener
  • 然后实现beforeMarshal接口
  • 利用反射将空属性赋值默认值
public class MarshallerListener extends Marshaller.Listener {
    @Override
    public void beforeMarshal(Object source) {
        super.beforeMarshal(source);
        Field[] fields = source.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);

            try {
                if (field.getType().equals(String.class) && field.get(source) == null){
                    field.set(source,"");
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

然后在Marshaller转换xml时设置这个监听器就可以了

marshaller.setListener(new MarshallerListener());

到此就能达到输出空属性的XML了。

总结

利用好javax.xml.bind包,咱们就能很简单的实现对象和XML的相互转换了。

文中如有不足之处,欢迎指正!一起交流,一起学习,一起成长 ^v^