【问题标题】:Prevent Jackson XML mapper from adding wstxns to namespaces防止 Jackson XML 映射器将 wstxns 添加到命名空间
【发布时间】:2019-09-22 19:21:14
【问题描述】:

当将对象序列化为 XML 并为属性指定命名空间时,使用 @JacksonXmlRootElement(namespace = "http://...") Jackson 会将“wstxns1”附加或添加到命名空间。例如,假设我们有这些类:

VtexSkuAttributeValues.java

@JacksonXmlRootElement(localName = "listStockKeepingUnitName")
public class VtexSkuAttributeValues {

    @JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
    @JacksonXmlElementWrapper(useWrapping = false)
    private VtexSkuAttributeValue[] stockKeepingUnitFieldNameDTO;

    public VtexSkuAttributeValue[] getStockKeepingUnitFieldNameDTO() {
        return stockKeepingUnitFieldNameDTO;
    }

    public void setValues(VtexSkuAttributeValue[] values) {
        this.stockKeepingUnitFieldNameDTO = values;
    }
}

VtexSkuAttributeValue.java

@JacksonXmlRootElement(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public class VtexSkuAttributeValue {

    private String fieldName;
    private FieldValues fieldValues;
    private int idSku;

    public int getIdSku() {
        return idSku;
    }

    public String getFieldName() {
        return fieldName;
    }

    public FieldValues getFieldValues() {
        return fieldValues;
    }

    public void setIdSku(int idSku) {
        this.idSku = idSku;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public void setFieldValues(FieldValues fieldValues) {
        this.fieldValues = fieldValues;
    }

    @JacksonXmlRootElement(localName = "fieldValues", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
    public static class FieldValues {
        @JacksonXmlProperty(namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")
        @JacksonXmlElementWrapper(useWrapping = false)
        public String[] string;

        public String[] getString() {
            return string;
        }

        public void setValues(String[] values) {
            this.string = values;
        }
    }
}

然后我使用XmlMapper 序列化并获取:

<listStockKeepingUnitName>
    <wstxns1:StockKeepingUnitFieldNameDTO xmlns:wstxns1="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
        <fieldName>talle</fieldName>
        <fieldValues>
            <wstxns2:string xmlns:wstxns2="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</wstxns2:string>
        </fieldValues>
        <idSku>258645</idSku>
    </wstxns1:StockKeepingUnitFieldNameDTO>
    <wstxns3:StockKeepingUnitFieldNameDTO xmlns:wstxns3="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
        <fieldName>color</fieldName>
        <fieldValues>
            <wstxns4:string xmlns:wstxns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6244</wstxns4:string>
        </fieldValues>
        <idSku>258645</idSku>
    </wstxns3:StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>

即使这是有效的 XML,我正在使用的 Web 服务也不接受它。我调试了它,这是由于杰克逊出于某种原因添加的标签中的wstxns 属性。 有没有办法阻止杰克逊将其添加到标签中。我能想到的唯一解决方法是对生成的 XML 执行 string.replaceAll ,但这显然并不理想。

【问题讨论】:

  • 那些“wstxns 属性”来自JacksonXmlRootElement 注释中的namespace 属性。如果省略namespace 属性,是否会得到所需的输出?
  • @dnault 我不能省略它,因为它是网络服务所需要的。

标签: java xml jackson jackson-dataformat-xml


【解决方案1】:

XML Jackson 使用javax.xml.stream.XMLStreamWriter。您可以配置该类的实例并为命名空间定义自己的前缀,并在需要时设置默认值。为此,我们需要扩展com.fasterxml.jackson.dataformat.xml.XmlFactory 类并覆盖创建XMLStreamWriter 实例的方法。示例实现如下所示:

class NamespaceXmlFactory extends XmlFactory {

    private final String defaultNamespace;
    private final Map<String, String> prefix2Namespace;

    public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
        this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
        this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
    }

    @Override
    protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
        XMLStreamWriter writer = super._createXmlWriter(ctxt, w);
        try {
            writer.setDefaultNamespace(defaultNamespace);
            for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
                writer.setPrefix(e.getKey(), e.getValue());
            }
        } catch (XMLStreamException e) {
            StaxUtil.throwAsGenerationException(e, null);
        }
        return writer;
    }
}

你可以像下面这样使用它:

import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

public class XmlMapperApp {

    public static void main(String[] args) throws Exception {
        String defaultNamespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
        Map<String, String> otherNamespaces = Collections.singletonMap("a", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");

        XmlMapper xmlMapper = new XmlMapper(new NamespaceXmlFactory(defaultNamespace, otherNamespaces));
        xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);

        System.out.println(xmlMapper.writeValueAsString(new VtexSkuAttributeValues()));
    }
}

VtexSkuAttributeValues 类中你可以声明:

public static final String DEF_NMS = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";

并将它用于应该用作默认命名空间的每个类和字段。例如:

@JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = DEF_NMS)

对于您不想更改名称的属性,您可以使用:

@JacksonXmlProperty(namespace = VtexSkuAttributeValues.DEF_NMS)

上面的代码打印一些随机数据:

<listStockKeepingUnitName>
  <StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
    <fieldName>Name1</fieldName>
    <fieldValues>
      <a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
    </fieldValues>
    <idSku>123</idSku>
  </StockKeepingUnitFieldNameDTO>
  <StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
    <fieldName>Name1</fieldName>
    <fieldValues>
      <a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
    </fieldValues>
    <idSku>123</idSku>
  </StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>

如果不是您想要的,您可以使用该代码并尝试其他可用的方法来配置此实例。

使用2.9.9 版本中的Jackson 创建此示例。

【讨论】:

  • 这是一个很好的例子,展示了 Jackson XML 的内部工作原理,现在我觉得我应该更好地表达我的问题。有没有办法完全阻止杰克逊向命名空间添加前缀?如果我设置一个空字符串作为前缀,Jackson 默认为wstxns。如果我将其设置为 null,我会得到 NullPointerException
  • @Morgan, Jackson 只是其他库的包装器,在这种情况下是 woodstox。但是,通常您不能在全局范围内同时拥有多个默认命名空间。使用不带前缀的默认命名空间xmlns。如果您希望始终使用不带任何前缀的xmlns,则需要在序列化期间更改默认命名空间,因此在需要命名空间的每个级别上,我们都需要将其设置为默认值。我认为我的示例将允许您生成可接受的XML
【解决方案2】:

这似乎是缺失的部分。它允许您设置前缀和命名空间。

   static class NamespaceXmlFactory extends XmlFactory {

    private final String defaultNamespace;
    private final Map<String, String> prefix2Namespace;

    public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
        this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
        this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
    }

    @Override
    protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
        XMLStreamWriter2 writer = (XMLStreamWriter2)super._createXmlWriter(ctxt, w);
        try {
            writer.setDefaultNamespace(defaultNamespace);
            writer.setPrefix("xsi", "http://www.w3.org/2001/XMLSchema-instance");
            for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
                writer.setPrefix(e.getKey(), e.getValue());
            }
        } catch (XMLStreamException e) {
            StaxUtil.throwAsGenerationException(e, null);
        }
        return writer;
    }
}

我唯一剩下的问题是

    @JacksonXmlProperty(localName = "@xsi.type", isAttribute = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
@JsonProperty("@xsi.type")
private String type;

创建以下输出:

仍在尝试解决如何将其设置为 xsi:type="networkObjectGroupDTO" 。

【讨论】:

  • 是的,我知道构造函数在上面的代码中是没用的。这是为了测试
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2017-09-21
  • 2016-05-10
  • 2014-08-31
  • 2018-10-12
  • 2013-09-21
相关资源
最近更新 更多