0

I'm using Jackson v2 in a JAX-RS app, and I'm trying to figure out if it is possible to configure "ObjectMapper" to drop fields with transient modifier, but only during serialisation.

Here's a DTO to illustrate my use case (getters & setters omitted)

public class User {
    private String email;
    private transient String password;
}

With the above DTO in mind, I would like to have an object mapper that drops the password field when serialising, and includes it during deserialisation.

The only thing I could find regarding transient is:

MapperFeature.PROPAGATE_TRANSIENT_MARKER

But that config isn't relevant to my requirements.

P.S - No annotation are to be used, as the DTO's are not to be coupled to any library, plus I don't necessarily have control over them.

elad.chen
  • 2,375
  • 5
  • 25
  • 37
  • I don't understand, if you don't serialize attribute A, what would be deserialized for A, if you include it then? – Curiosa Globunznik Oct 19 '19 at 11:25
  • https://stackoverflow.com/questions/57184389/jackson-serialiser-ignore-field-at-serialisation-time - the use case I'm referring to is the the same in that thread (7 years ago). I'm hoping for a solution that doesn't involve annotations. – elad.chen Oct 19 '19 at 11:31
  • What you describe doesn't sound anything like the linked question. The accepted answer there doesn't use annotiations for serialization/deserialization. The annotations there are `@Override`, `@SuppressWarnings` and `@Test`. And you didn't answer my question. – Curiosa Globunznik Oct 19 '19 at 11:39
  • When I'm saying is that an incoming JSON with the fields email, and password should be constructed into an an instance of User, with its email & password populated — When converting the instance of User back to JSON, I would like transient fields to be left out. (The same behaviour should apply to any type not just user.) – elad.chen Oct 19 '19 at 11:44

1 Answers1

1

Don't know, looks like transient attribute modifier plus PROPAGATE_TRANSIENT_MARKER just does it. Deserialization is straight forward. There's some fuss with the transient marker during serialization, though. You need to provide getters for the fields, otherwise you'll be stuck with an exception "Unrecognized field ... not marked as ignorable".

Reacting to additional requirements I added a version using jackson mixins. Note that the mixin version produces json with an empty password field, while the PROPAGATE_TRANSIENT_MARKER version produces no field at all.

PROPAGATE_TRANSIENT_MARKER version

public class SerializeDeserializeAttributes {
    private final ObjectMapper mapper = new ObjectMapper();

public <T> T fromJson(String json, Class<T> c) throws IOException {
    synchronized (mapper) {
        mapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, false);
        return mapper.readValue(json, c);
    }
}

public String toJson(Object o) throws JsonProcessingException {
    synchronized (mapper) {
        mapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);
        return mapper.writeValueAsString(o);
    }
}

    private static final String jsonFull = "{\"name\":\"A\",\"email\":\"a@a\",\"password\":\"a\",\"width\":1,\"height\":1}";
    private static final String jsonPartial = "{\"name\":\"A\",\"email\":\"a@a\",\"width\":1,\"height\":1}";
    private static final User user = new User("A", "a@a", "a", 1, 1);

    @Test
    public void serializeDeserialize() throws IOException {
        assertEquals(user, fromJson(jsonFull, User.class));
        assertEquals(jsonPartial, toJson(user));
        assertEquals(user, fromJson(jsonFull, User.class));
        assertEquals(jsonPartial, toJson(user));
    }
}

Mixin version

public class SerializeDeserializeAttributesMixin {

    public abstract class UserMixin {
        @JsonSerialize(using = PwdSerializer.class)
        transient String password;
    }

    static class PwdSerializer extends StdSerializer<String> {

        public PwdSerializer() {
            this(String.class);
        }

        private PwdSerializer(Class<String> t) {
            super(t);
        }

        @Override
        public void serialize(String s, JsonGenerator jg, SerializerProvider sp) throws IOException {
            jg.writeString("");
        }
    }

    private static final String jsonFull = "{\"name\":\"A\",\"email\":\"a@a\",\"password\":\"a\",\"width\":1,\"height\":1}";
    private static final String jsonPartialMixin = "{\"name\":\"A\",\"email\":\"a@a\",\"password\":\"\",\"width\":1,\"height\":1}";
    private static final User user = new User("A", "a@a", "a", 1, 1);
    private static final ObjectMapper mapperMixin = new ObjectMapper();

    static {
        mapperMixin.addMixIn(User.class, UserMixin.class);
    }


    @Test
    public void serializeDeserializeUsingMixin() throws IOException {
        assertEquals(user, mapperMixin.readValue(jsonFull, User.class));
        assertEquals(jsonPartialMixin, mapperMixin.writeValueAsString(user));
    }

}

That's the User class.

class User {
    private String name;
    private String email;
    private transient String password;
    private int width;
    private int height;

    public User() {
    }

    User(String name, String email, String password, int width, int height) {
        this.name = name;
        this.email = email;
        this.password = password;
        this.width = width;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public String getPassword() {
        return password;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Float.compare(user.width, width) == 0 &&
                Float.compare(user.height, height) == 0 &&
                Objects.equals(name, user.name) &&
                Objects.equals(email, user.email) &&
                Objects.equals(password, user.password);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email, password, width, height);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", password='" + password + '\'' +
                ", width=" + width +
                ", height=" + height +
                '}';
    }
}
Curiosa Globunznik
  • 3,129
  • 1
  • 16
  • 24
  • The only reason the posted code works, is the fact that "fromJson" method is not re-using the same objectMapper instance — I'm afraid creating more than a single instance it a no-op for me. – elad.chen Oct 19 '19 at 16:24
  • architects ‍♂️. I think I'll just use mixins. I cann't mark this is an accepted answer, given that there its not the one I think is right. If you'll change your post to include mixins, I'll accept that. – elad.chen Oct 19 '19 at 16:48
  • I guess this does answer the question - But not quite thread safe :) Either way, its still not vary viable when combined with JAX-RS & a ContextResolver provider. I'll accept this since my question didn't mention the mentioned requirements. – elad.chen Oct 19 '19 at 16:56
  • I added a jackson mixin version. – Curiosa Globunznik Oct 20 '19 at 08:10