6

I'm using Jersey 1.17 with com.sun.jersey.api.json.POJOMappingFeature set to true and I'm POSTing an object that looks like this:

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

@XmlRootElement
public class ByteArrayWithOverloadedSetterObject {

    private byte[] data;

    public void setData(byte[] data) {
        this.data = data;
    }

    @XmlTransient
    @JsonIgnore
    public void setData(String data) {
        this.data = data.getBytes();
    }

    public byte[] getData() {
        return data;
    }
}

My Resource has this method:

@POST
@Path("postByteArrayWithOverloadedSetterObject")
public byte[] sendByteArrayWithOverloadedSetterObject(ByteArrayWithOverloadedSetterObject byteArrayWithOverloadedSetterObject) {
    if (byteArrayWithOverloadedSetterObject.getData() == null) {
        log.warn("data was null!");
    }
    return byteArrayWithOverloadedSetterObject.getData();
}

And my client side unit tests are written like this:

@Test
public void whenSendingStringDataThenGetDataBack() {
    ByteArrayWithOverloadedSetterObject byteArrayWithOverloadedSetterObject = new ByteArrayWithOverloadedSetterObject();
    byteArrayWithOverloadedSetterObject.setData("foobar");
    byte[] data = new AuthClient().postJaxbToUrl(RestApiUriBuilder.TEST_REST_PATH + "/postByteArrayWithOverloadedSetterObject", byteArrayWithOverloadedSetterObject, byte[].class);
    assertNotNull(data);
}

@Test
public void whenSendingByteDataThenGetDataBack() {
    ByteArrayWithOverloadedSetterObject byteArrayWithOverloadedSetterObject = new ByteArrayWithOverloadedSetterObject();
    byteArrayWithOverloadedSetterObject.setData(new byte[]{0, 1, 1, 0});
    byte[] data = new AuthClient().postJaxbToUrl(RestApiUriBuilder.TEST_REST_PATH + "/postByteArrayWithOverloadedSetterObject", byteArrayWithOverloadedSetterObject, byte[].class);
    assertNotNull(data);
}

Both of these tests are failing with a message that says "postByteArrayWithOverloadedSetterObject returned a response status of 204 No Content" and the server is logging "data was null!" for each. Why isn't the byte array data being serialized over the wire? I'm sending this as JSON.

If I comment out the @XmlTransient/@JsonIgnore annotations, I get this error: "java.lang.RuntimeException: Conflicting setter definitions for property "data"".

In my real situation, I can't easily modify the API of ByteArrayWithOverloadedSetterObject. But since this is just a test, I can see that things work correctly if I rename the second setter and the object becomes this:

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

@XmlRootElement
public class ByteArrayWithOverloadedSetterObject {

    private byte[] data;

    public void setData(byte[] data) {
        this.data = data;
    }

    @XmlTransient
    @JsonIgnore
    public void setData2(String data) {  //RENAME HERE.  Now tests pass.
        this.data = data.getBytes();
    }

    public byte[] getData() {
        return data;
    }
}

Since I can't change the API of ByteArrayWithOverloadedSetterObject, is there any other work around to this?

Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356

1 Answers1

8

This is the solution:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

@XmlRootElement
public class ByteArrayWithOverloadedSetterObject {

    private byte[] data;

    @JsonProperty //I ADDED THIS
    public void setData(byte[] data) {
        this.data = data;
    }

    @XmlTransient
    @JsonIgnore
    public void setData(String data) {
        this.data = data.getBytes();
    }

    public byte[] getData() {
        return data;
    }
}

Now my tests pass.

Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356
  • And the explanation here is that without additional annotation Jackson thinks that user simply wants to completely ignore property "data": but what is needed here is "cleaved" handling; one setter is to be kept, other removed. – StaxMan Jun 12 '14 at 20:40
  • @StaxMan in the end I'm glad there's a workaround, but I would think the way I originally annotated it should *mean* that it's "cleaved". – Daniel Kaplan Jun 12 '14 at 20:41
  • 2
    there is quite a bit of history here; before 1.8 Jackson actually did not combine annotations and all accessors had separate handling. This works for some use cases, but was cumbersome for many others, as well as differed from how JAXB handles things. So I agree that behavior here is not intuitive, but it is actually quite difficult to come up with solid rules that actually work in various different cases. – StaxMan Jun 12 '14 at 20:49