Запретить отображению XML Джексона добавлять wstxns в пространства имен

При сериализации объектов в XML и указании пространств имен для свойств с помощью @JacksonXmlRootElement(namespace = "http://...") Джексон будет добавлять или добавлять «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, веб-служба, с которой я работаю, не принимает его. Я отладил его, и это связано со свойствами wstxns в тегах, которые по какой-то причине добавляет Джексон. Есть ли способ запретить Джексону добавлять это в теги. Единственный обходной путь, который я мог придумать, - это выполнить string.replaceAll для полученного XML, но он, очевидно, не идеален.


person Morgan    schedule 22.09.2019    source источник
comment
Эти свойства wstxns взяты из атрибута namespace в аннотации JacksonXmlRootElement. Вы получите желаемый результат, если опустите атрибут namespace?   -  person dnault    schedule 23.09.2019
comment
@dnault Я не могу пропустить это, потому что это требуется веб-службой.   -  person Morgan    schedule 23.09.2019


Ответы (2)


Для записи 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>

Если это не то, что вы хотите, вы можете поиграть с этим кодом и попробовать другие доступные вам методы настройки этого экземпляра.

Для создания этого примера использовался Jackson в версии 2.9.9.

person Michał Ziober    schedule 24.09.2019
comment
Это очень хороший пример, который показывает внутреннюю работу XML Джексона, и теперь я чувствую, что должен был лучше сформулировать свой вопрос. Есть ли способ полностью запретить Джексону добавлять префикс в пространство имен? Если я установлю пустую строку в качестве префикса, Джексон по умолчанию будет wstxns. Если я установлю значение null, я получу NullPointerException - person Morgan; 25.09.2019
comment
@Morgan, Jackson - это просто оболочка для другой библиотеки, в данном случае для woodstox. Но, как правило, вы не можете одновременно иметь более одного пространства имен по умолчанию в глобальной области видимости. Для записи по умолчанию используется пространство имен xmlns без префиксов. Если вы хотите всегда иметь xmlns без каких-либо префиксов, вам нужно изменить пространство имен по умолчанию во время сериализации, поэтому на каждом уровне, когда требуется пространство имен, нам нужно установить его по умолчанию. Я подумал, что мой пример позволит вам сгенерировать приемлемый XML. - person Michał Ziober; 25.09.2019

Кажется, это недостающая часть. Это позволяет вам установить префикс и пространство имен.

   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;

Создает следующий вывод:

Still trying to resolve how to make it be xsi:type="networkObjectGroupDTO" instead.

person Jamie Snipes    schedule 09.12.2019
comment
Да, я знаю, что конструктор в приведенном выше коде бесполезен. Это было для пробы - person Jamie Snipes; 10.12.2019