BuilderViaUpdateTest.java
package tools.jackson.databind.deser.builder;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.exc.InvalidDefinitionException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static tools.jackson.databind.testutil.DatabindTestUtil.a2q;
import static tools.jackson.databind.testutil.DatabindTestUtil.verifyException;
/**
* Tests for use of {@code withValueToUpdate()} with builder-based deserializers.
* Passing a Builder instance is supported (see [databind#2100]); passing an
* already-built value is not.
*/
public class BuilderViaUpdateTest
{
@JsonDeserialize(builder=SimpleBuilderXY.class)
static class ValueClassXY
{
public final int x, y;
protected ValueClassXY(int x, int y) {
this.x = x;
this.y = y;
}
}
static class SimpleBuilderXY
{
public int x, y;
public SimpleBuilderXY withX(int x0) {
this.x = x0;
return this;
}
public SimpleBuilderXY withY(int y0) {
this.y = y0;
return this;
}
public ValueClassXY build() {
return new ValueClassXY(x, y);
}
}
// Mirrors the reproducer from [databind#2100]
@JsonDeserialize(builder = POJO2100.Builder.class)
static class POJO2100
{
public final int id;
public final String value;
POJO2100(int id, String value) {
this.id = id;
this.value = value;
}
static class Builder
{
int id;
String value;
public Builder withId(int id) {
this.id = id;
return this;
}
public Builder withValue(String value) {
this.value = value;
return this;
}
public POJO2100 build() {
return new POJO2100(id, value);
}
}
}
private final static ObjectMapper MAPPER = new ObjectMapper();
// Passing the built value itself remains unsupported: builder-backed POJOs
// are typically immutable and we have no way to re-populate builder state.
@Test
public void testBuilderUpdateWithValue() throws Exception
{
try {
/*ValueClassXY value =*/ MAPPER.readerFor(ValueClassXY.class)
.withValueToUpdate(new ValueClassXY(6, 7))
.readValue(a2q("{'x':1,'y':2}"));
fail("Should not have passed");
} catch (InvalidDefinitionException e) {
verifyException(e, "Deserialization of");
verifyException(e, "by passing existing instance");
verifyException(e, "ValueClassXY");
// Hint should name the Builder class as the expected update-value type
verifyException(e, "pass a Builder");
verifyException(e, "SimpleBuilderXY");
}
}
// [databind#2100]: passing a Builder instance merges JSON on top of its
// current state and then produces the built value.
@Test
public void testBuilderUpdateWithBuilder() throws Exception
{
SimpleBuilderXY builder = new SimpleBuilderXY();
builder.x = 10;
builder.y = 20;
ValueClassXY result = MAPPER.readerFor(ValueClassXY.class)
.withValueToUpdate(builder)
.readValue(a2q("{'x':1}"));
// Builder state: x overridden by JSON, y preserved from pre-set state
assertEquals(1, builder.x);
assertEquals(20, builder.y);
// And build() produces a value reflecting the merged builder
assertNotNull(result);
assertEquals(1, result.x);
assertEquals(20, result.y);
}
// [databind#2100]: verbatim reproducer from the issue report.
@Test
public void testIssue2100Reproducer() throws Exception
{
POJO2100.Builder builder = new POJO2100.Builder().withId(1).withValue("1");
POJO2100 result = MAPPER.readerFor(POJO2100.class)
.withValueToUpdate(builder)
.readValue(a2q("{'value':'2'}"));
assertEquals(1, builder.id);
assertEquals("2", builder.value);
assertNotNull(result);
assertEquals(1, result.id);
assertEquals("2", result.value);
}
}