使用JAXB反序列化含<sup>内部标签的XML时内容丢失问题求助
解决JAXB反序列化时丢失嵌套标签内文本的问题
我来帮你搞定这个JAXB反序列化的坑!首先得搞清楚为什么你只能拿到"Watching TV"——你的<Hobbies>标签里嵌套了<sup>子标签,默认的JAXB字符串映射只会提取最后一个独立文本节点,前面的"Playing Cricket "和<sup>里的"+"都被忽略了,只留下了子标签后面的文本。
下面给你两种可行的解决方案,你可以根据需求选择:
方案一:自定义DomHandler递归提取所有文本节点
如果你的需求是把所有文本内容合并(比如得到"Playing Cricket + Watching TV"),可以用这种方法。先写一个能遍历所有节点收集文本的DomHandler:
import javax.xml.bind.annotation.DomHandler; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.bind.ValidationEventHandler; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class TextExtractingDomHandler implements DomHandler<String, DOMSource> { @Override public DOMSource createUnmarshaller(ValidationEventHandler errorHandler) { return new DOMSource(); } @Override public String getElement(DOMSource domSource) { Node node = domSource.getNode(); StringBuilder sb = new StringBuilder(); collectAllText(node, sb); return sb.toString().trim(); } // 递归遍历所有节点,收集文本内容 private void collectAllText(Node node, StringBuilder sb) { if (node.getNodeType() == Node.TEXT_NODE) { sb.append(node.getNodeValue().trim()).append(" "); } NodeList childNodes = node.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { collectAllText(childNodes.item(i), sb); } } @Override public Source marshal(String text, ValidationEventHandler errorHandler) { // 如果不需要序列化功能,直接抛出异常即可 throw new UnsupportedOperationException("序列化暂不支持"); } }
然后在实体类的hobbies字段上标注使用这个Handler:
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlAnyElement; @XmlRootElement(name = "School") public class School { private Student student; // getter和setter方法省略 public static class Student { private String name; // 用自定义DomHandler处理Hobbies字段 @XmlAnyElement(TextExtractingDomHandler.class) private String hobbies; // getter和setter方法省略 } }
方案二:用XmlAdapter处理元素节点
这种方法和DomHandler思路类似,但用适配器的方式实现,也能达到同样的效果:
先写一个自定义适配器:
import javax.xml.bind.annotation.adapters.XmlAdapter; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class HobbiesTextAdapter extends XmlAdapter<Element, String> { @Override public String unmarshal(Element element) throws Exception { StringBuilder sb = new StringBuilder(); NodeList nodes = element.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() == Node.TEXT_NODE) { sb.append(node.getNodeValue().trim()).append(" "); } else if (node.getNodeType() == Node.ELEMENT_NODE) { // 提取子元素里的文本内容 sb.append(node.getTextContent().trim()).append(" "); } } return sb.toString().trim(); } @Override public Element marshal(String v) throws Exception { throw new UnsupportedOperationException("序列化暂不支持"); } }
然后在实体类中使用这个适配器:
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement(name = "School") public class School { private Student student; // getter和setter省略 public static class Student { private String name; @XmlJavaTypeAdapter(HobbiesTextAdapter.class) private String hobbies; // getter和setter省略 } }
额外需求:保留HTML标签(比如<sup>)
如果你想完整保留<Hobbies>里的HTML标签(得到"Playing Cricket + Watching TV"),可以修改DomHandler,把节点转成XML字符串:
import javax.xml.bind.annotation.DomHandler; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.bind.ValidationEventHandler; import org.w3c.dom.Node; import java.io.StringWriter; public class HtmlPreservingDomHandler implements DomHandler<String, DOMSource> { @Override public DOMSource createUnmarshaller(ValidationEventHandler errorHandler) { return new DOMSource(); } @Override public String getElement(DOMSource domSource) { try { Node node = domSource.getNode(); StringWriter writer = new StringWriter(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.transform(new DOMSource(node), new StreamResult(writer)); return writer.toString().trim(); } catch (Exception e) { throw new RuntimeException("转换节点为字符串失败", e); } } @Override public Source marshal(String text, ValidationEventHandler errorHandler) { throw new UnsupportedOperationException("序列化暂不支持"); } }
然后把实体类里的@XmlAnyElement换成这个Handler就行。
你之前用DomHandler没成功,大概率是因为没有递归处理所有子节点,只读取了直接文本节点,导致前面的内容丢失。按照上面的写法应该就能解决问题啦!
内容的提问来源于stack exchange,提问作者JITEN PATEL
相关产品推荐
相关产品推荐

