Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson xml module: deserializing immutable type with a @JacksonXmlText property

Tags:

java

xml

jackson

I want to serialize an immutable type both as json and as xml:

The serialized JSON is like:

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

and the serialized xml is like:

 <asText>_text_</asText>

(note that the text is the xml's element text)

The java object is like:

@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;
    }
}

beware that the _text property is final (so the object is immutable) and it's annotated with @JacksonXmlText in order to be serialized as the xml element's text

Being the object immutable, a constructor from the text is needed and the constructor's argument must be annotated with @JsonProperty

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

When serializing / deserializing to/from JSON everything works fine ... the problem arises when serializing / deserializing to/from 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());

The exception is:

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

It seems that the xml module needs the object's constructor to be annotated like:

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

BUT this does NOT even works:

    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)

The annotation @JsonProperty("text") at the constructor's argument is needed to deserialize from JSON

... how can i make this to work

like image 885
futuretelematics Avatar asked Aug 15 '17 09:08

futuretelematics


3 Answers

Try adding a public getter for the property. I believe that should fix the deserialization issue.

@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;
    }
}

Actually, it works without adding a getter too, with these versions of Lombak & Jackson.

<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>
like image 96
Arul Dhesiaseelan Avatar answered Oct 09 '22 05:10

Arul Dhesiaseelan


I had same issue with error "no delegate- or property-based Creator". In my case it was problem with Immutables version 2.5.6. I have fixed it by downgrade to version 2.5.5. Version 2.5.6 is available in mvnrepository.com but on official page is as stable version marked 2.5.5.

like image 20
Jaroslav Kuboš Avatar answered Oct 09 '22 05:10

Jaroslav Kuboš


Update 2018

This hack worked in 2.9.0 but it seems to stop working after this commit. It is not clear if there is an easy way to make it work again.


It looks like the main reason for your issue is that you try to use JSON and XML serialization at the same time but with different configurations. Unfortunately XmlMapper inherits from ObjectMapper and inherits all the JSON-specific configuration (and you can override it but can not clear it with XML-specific annotations) which is the reason for your conflict. It seems that the simplest way to work this around is to use @JsonAlias annotation in the constructor. It is a bit hacky but it works. Here is a minimal example (without Lombok) that works for me:

@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 + '\'' +
                '}';
    }
}

Note that I also added @JsonIgnore to the getter because else I didn't get XML format you requested (and you can do the same using Lombok's onMethod as described at onX).

For a simple test:

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);
}

I get the following output:

Serialized Xml
<asText>123_text_</asText>
Read from Xml: AsText{_text='123_text_'}

like image 3
SergGr Avatar answered Oct 09 '22 04:10

SergGr