28

I have this JSON-Content:

{"color":null}

And I want to make these Java-Objects out of it (and vice-versa):

Container
|- List<Entry> entries
   |- Entry
      |- String key = "color"
      |- String value = null

My current solution always deserializes "color":null to an empty String. I found other solutions, that will instead deserialize null or empty String to null.

How can I get MOXy (or any other jaxb implementation) to deserialize null as null and empty Strings to empty Strings?


I am using this code:

import java.io.ByteArrayOutputStream;
import java.util.*;

import javax.xml.bind.*;
import javax.xml.bind.annotation.*;

import org.eclipse.persistence.internal.oxm.ByteArraySource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.oxm.annotations.*;
import org.junit.*;

class Container {

    @XmlVariableNode("key")
    List<Entry> entries = new ArrayList<Entry>();   
}

class Entry {

    @XmlTransient
    public String key;

    @XmlValue
    @XmlNullPolicy(nullRepresentationForXml=XmlMarshalNullRepresentation.XSI_NIL, xsiNilRepresentsNull=false)
    public String value;
}

public class D {

    /** THIS TEST FAILS!!! */

    @Test
    public void unmarshallNull() throws Exception {
        Assert.assertEquals(null, unmarshall("xml", "<root><color xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/></root>"));
        Assert.assertEquals(null, unmarshall("json", "{\"color\":null}"));
    }

    /* All other tests are passing. */

    @Test
    public void unmarshallEmpty() throws Exception {
        Assert.assertEquals("", unmarshall("xml", "<root><color></color></root>"));
        Assert.assertEquals("", unmarshall("json", "{\"color\":\"\"}"));
    }

    @Test
    public void unmarshallValue() throws Exception {
        Assert.assertEquals("red", unmarshall("xml", "<root><color>red</color></root>"));
        Assert.assertEquals("red", unmarshall("json", "{\"color\":\"red\"}"));
    }

    @Test
    public void marshallNull() throws Exception {
        Assert.assertEquals("<color xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/>", marshall("xml", null));
        Assert.assertEquals("{\"color\":null}", marshall("json", null));
    }

    @Test
    public void marshallEmpty() throws Exception {
        Assert.assertEquals("<color></color>", marshall("xml", ""));
        Assert.assertEquals("{\"color\":\"\"}", marshall("json", ""));
    }

    @Test
    public void marshallValue() throws Exception {
        Assert.assertEquals("<color>red</color>", marshall("xml", "red"));
        Assert.assertEquals("{\"color\":\"red\"}", marshall("json", "red"));
    }

    private static String marshall(String format, String value) throws JAXBException {

        // prepare
        JAXBContext jc = JAXBContext.newInstance(Container.class);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(JAXBContextProperties.MEDIA_TYPE, "application/"+format);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        marshaller.setProperty(JAXBContextProperties.JSON_INCLUDE_ROOT, false);

        // define example data
        Container detail = new Container();
        Entry entry = new Entry();
        entry.key = "color";
        entry.value = value;
        detail.entries.add(entry);

        // marshall
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        marshaller.marshal(detail, outputStream);
        return outputStream.toString();
    }

    private static String unmarshall(String format, String raw) throws JAXBException {

        // prepare
        JAXBContext jc = JAXBContext.newInstance(Container.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setProperty(JAXBContextProperties.MEDIA_TYPE, "application/"+format);
        unmarshaller.setProperty(JAXBContextProperties.JSON_INCLUDE_ROOT, false);

        // unmarshall
        Container container = unmarshaller.unmarshal(new ByteArraySource(raw.getBytes()), Container.class).getValue();
        return container.entries.get(0).value;
    }
}

It works for XML but fails for JSON:

Test failure: unmarshallNull
java.lang.AssertionError: expected:<null> but was:<>

I also configured MOXy as my jaxb-provider for the relevant package (jaxb.properties):

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

I'm using MOXy 2.22.1 and Java8 (but same behaviour with 2.18, 2.19, 2.20, 2.21, 2.22). My Maven pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>x</groupId>
    <artifactId>x</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-moxy</artifactId>
            <version>2.22.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
slartidan
  • 20,403
  • 15
  • 83
  • 131

1 Answers1

5

There are things you might want to try.

First, try using @XmlElement(nillable=true) instead of XmlNullPolicy annotation or setting the emptyNodeRepresentsNull parameter.

Second, you might want to write your own XmlAdapter which will do pretty much the same thing - unmarshall empty string to null. The main difference is that you can also manually configure which fields will be unmarshalled with your adapter and which will not, therefore retaining the current behaviour. Please refer to this answer by Blaise Doughan to see how do you wire a custom XmlAdapter to your configuration.

Third, as far as I know Jackson doesn't have this problem. See this answer ot this wiki page about how to set up a custom deserializer for a field in case it does.

Community
  • 1
  • 1
Jeor Mattan
  • 475
  • 3
  • 10
  • Using `@XmlElement(nillable=true)` on either `value` or `entries` does not solve this issue. – slartidan Dec 17 '15 at 15:46
  • I tried to implement a custom `XmlAdapter` (on the `value` field). But it gets empty strings for both: *nulls* and *empty strings*. I cannot distinguish between them anymore in an `XmlAdapter`. – slartidan Dec 17 '15 at 15:51
  • @slartidan what I forgot to ask initially, do you need to keep MOXy? Jackson doesn't have this problem. – Jeor Mattan Dec 17 '15 at 21:18
  • Moving to Jackson would be an option. Could you help me to migrate the example from MOXy to Jackson? Do I need `@JsonAnySetter`? Will Serialization also be possible? – slartidan Dec 18 '15 at 07:05
  • Jackson does not provide a solution for this problem. The other sugestions also did not work. – slartidan Jan 19 '16 at 08:05