Модуль Jackson xml: десериализация неизменяемого типа с использованием свойства @JacksonXmlText

Я хочу сериализовать неизменяемый тип как json, так и как xml:

Сериализованный JSON похож:

{
    "text" : "... the text..."
}

и сериализованный xml выглядит так:

 <asText>_text_</asText>

(обратите внимание, что текст представляет собой текст элемента xml)

Объект java выглядит как:

@JsonRootName("asText")
@Accessors(prefix="_")
public static class AsText {

    @JsonProperty("text") @JacksonXmlText
    @Getter private final String _text;

    public AsText(@JsonProperty("text") final String text) {
        _text = text;
    }
}

остерегайтесь, что свойство _text final (поэтому объект неизменен), и он аннотируется с помощью @JacksonXmlText, чтобы быть сериализованным как текст элемента xml

Будучи объектом неизменяемым, требуется конструктор из текста, и аргумент конструктора должен быть аннотирован с помощью @JsonProperty

    public AsText(@JsonProperty("text") final String text) {
        _text = text;
    }

При сериализации/десериализации в/из JSON все работает отлично ... проблема возникает при сериализации/десериализации в/из XML:

 // create the object
 AsText obj = new AsText("_text_");

 // init the mapper
 XmlMapper mapper = new XmlMapper();

 // write as xml
 String xml = mapper.writeValueAsString(obj);
 log.warn("Serialized Xml\n{}",xml);

 // Read from xml
 log.warn("Read from Xml:");
 AsText objReadedFromXml = mapper.readValue(xml,
                                              AsText.class);
 log.warn("Obj readed from serialized xml: {}",
          objReadedFromXml.getClass().getName());

Исключение составляет:

    com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "" (class r01f.types.url.UrlQueryStringParam), not marked as ignorable (2 known properties: "value", "name"])

Кажется, что для модуля xml требуется, чтобы конструктор объекта был аннотирован следующим образом:

    public AsText(@JsonProperty("") final String text) {
        _text = text;
    }

НО это НЕ РАБОТАЕТ:

    com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `test.types.SerializeAsXmlElementTextTest$AsText` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Аннотации @JsonProperty("text") в аргументе конструктора необходимы для десериализации из JSON

... как я могу заставить это работать

Ответы

Ответ 1

Попробуйте добавить публичный getter для свойства. Я считаю, что это должно устранить проблему десериализации.

@JsonRootName("asText")
@Accessors(prefix = "_")
public static class AsText {

    @JsonProperty("text")
    @JacksonXmlText
    @Getter
    private final String _text;

    public AsText(@JsonProperty("text") final String text) {
        _text = text;
    }

    public String getText() {
        return _text;
    }
}

На самом деле, он работает без добавления геттера тоже с этими версиями Ломбака и Джексона.

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.0</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.18</version>
    </dependency>
</dependencies>

Ответ 2

У меня была такая же проблема с ошибкой "нет создателя на основе делегата или основанного на свойствах". В моем случае это проблема с версией версии 2.6.6. Я исправил его с понижением до версии 2.5.5. Версия 2.5.6 доступна в mvnrepository.com, но на официальной странице установлена ​​стабильная версия с надписью 2.5.5.

Ответ 3

Похоже, что основной причиной вашей проблемы является то, что вы пытаетесь использовать сериализацию JSON и XML одновременно, но с разными конфигурациями. К сожалению, XmlMapper наследует от ObjectMapper и наследует всю специфичную для JSON конфигурацию (и вы можете переопределить ее, но не можете очистить ее с помощью аннотаций, специфичных для XML), что и является причиной вашего конфликта. Похоже, что самый простой способ работать над этим - использовать @JsonAlias аннотацию в конструкторе. Это немного хакерский, но он работает. Вот минимальный пример (без Lombok), который работает для меня:

@JsonRootName("asText")
public static class AsText {

    @JsonProperty("text")
    @JacksonXmlText
    private final String _text;

    public AsText(@JsonAlias("") @JsonProperty("text") final String text) {
        _text = text;
    }

    @JsonIgnore
    public String getText() {
        return _text;
    }

    @Override
    public String toString() {
        return "AsText{" +
                "_text='" + _text + '\'' +
                '}';
    }
}

Обратите внимание, что я также добавил @JsonIgnore в getter, потому что иначе я не получил запрошенный XML-формат (и вы можете сделать то же самое с помощью Lombok onMethod, как описано в onX).

Для простого теста:

public static void main(String[] args) throws IOException {
    // create the object
    AsText obj = new AsText("123_text_");

    // init the mapper
    //ObjectMapper mapper = new ObjectMapper();
    XmlMapper mapper = new XmlMapper();

    // write as xml
    String xml = mapper.writeValueAsString(obj);
    System.out.println("Serialized Xml\n" + xml);

    // Read from xml
    AsText objReadedFromXml = mapper.readValue(xml, AsText.class);
    System.out.println("Read from Xml: " + objReadedFromXml);
}

Я получаю следующий вывод:

Сериализованный Xml
<asText> 123_text_ </asText>
Чтение из Xml: AsText {_text = '123_text _'}