diff --git a/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java b/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java new file mode 100644 index 00000000..f2f4bae2 --- /dev/null +++ b/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java @@ -0,0 +1,15 @@ +package com.hubspot.rosetta.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicate that a field is a nested optional and deserialization will follow the nested optional rules. + */ +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@RosettaAnnotation +public @interface NestedOptional { +} diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java new file mode 100644 index 00000000..44a61d2e --- /dev/null +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java @@ -0,0 +1,56 @@ +package com.hubspot.rosetta.internal; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +public class NestedOptionalDeserializer extends StdDeserializer> { + + private final Class referencedClazz; + + public NestedOptionalDeserializer(Class> clazz, Class referencedClazz) { + super(clazz); + this.referencedClazz = referencedClazz; + } + + @Override + public Optional deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + + JsonNode root = mapper.readValue(jp, JsonNode.class); + + return convert(root, mapper); + } + + private Optional convert(JsonNode root, ObjectMapper mapper) throws IOException { + if (root.isNull()) { + return Optional.empty(); + } + + Iterator> it = root.fields(); + + if (!it.hasNext()) { + throw new IllegalArgumentException("The provided object has no fields: " + root); + } + + while (it.hasNext()) { + Entry entry = it.next(); + + if (!entry.getValue().isNull()) { + return Optional.of( + mapper.treeToValue(root, referencedClazz) + ); + } + } + + return Optional.empty(); + } + +} diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java index 6956c49d..63cc9412 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java @@ -11,8 +11,11 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; +import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; +import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.hubspot.rosetta.annotations.NestedOptional; import com.hubspot.rosetta.annotations.RosettaCreator; import com.hubspot.rosetta.annotations.RosettaDeserialize; import com.hubspot.rosetta.annotations.RosettaIgnore; @@ -65,6 +68,7 @@ public JsonSerializer findSerializer(Annotated a) { @SuppressWarnings("unchecked") public JsonDeserializer findDeserializer(Annotated a) { StoredAsJson storedAsJson = a.getAnnotation(StoredAsJson.class); + NestedOptional nestedOptional = a.getAnnotation(NestedOptional.class); RosettaDeserialize rosettaDeserialize = a.getAnnotation(RosettaDeserialize.class); if (storedAsJson != null && rosettaDeserialize != null) { throw new IllegalArgumentException("Cannot have @StoredAsJson as well as @RosettaDeserialize annotations on the same entry"); @@ -78,6 +82,12 @@ public JsonDeserializer findDeserializer(Annotated a) { return new StoredAsJsonDeserializer(a.getRawType(), a.getType(), empty, objectMapper); } + if (nestedOptional != null && (a instanceof AnnotatedMethod || a instanceof AnnotatedParameter)) { + ReferenceType refType = getReferenceType(a); + + return new NestedOptionalDeserializer(refType.getRawClass(), refType.getReferencedType().getRawClass()); + } + if (rosettaDeserialize != null) { Class klass = rosettaDeserialize.using(); if (klass != JsonDeserializer.None.class) { @@ -170,4 +180,14 @@ private Annotated getAnnotatedTypeFromAnnotatedMethod(AnnotatedMethod a) { throw new IllegalArgumentException("Cannot have @StoredAsJson on a method with no parameters AND no arguments"); } } + + private ReferenceType getReferenceType(Annotated a) { + if (a instanceof AnnotatedMethod) { + return (ReferenceType) ((AnnotatedMethod) a).getParameterType(0); + } else if (a instanceof AnnotatedParameter) { + return ReferenceType.upgradeFrom(a.getType(), a.getType().containedType(0)); + } else { + throw new IllegalArgumentException("Could not get ReferencedType."); + } + } } diff --git a/RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java b/RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java new file mode 100644 index 00000000..774e84cb --- /dev/null +++ b/RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java @@ -0,0 +1,27 @@ +package com.hubspot.rosetta.annotations; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import com.hubspot.rosetta.Rosetta; +import com.hubspot.rosetta.beans.InnerBean; +import com.hubspot.rosetta.beans.NestedOptionalBean; + +public class NestedOptionalTest { + + @Test + public void testAnnotatedFieldDeserialization() throws IOException { + String nestedOptionalBeanJson = "" + + "{\"firstNestedOptional\":{\"stringProperty\":\"value-1\"}, " + + "\"secondNestedOptional\":{\"firstStringProperty\": null, \"secondStringProperty\": null}}"; + + NestedOptionalBean bean = Rosetta.getMapper().readValue(nestedOptionalBeanJson, NestedOptionalBean.class); + + assertThat(bean.getFirstNestedOptional().map(InnerBean::getStringProperty)).contains("value-1"); + assertThat(bean.getSecondNestedOptional()).isEmpty(); + } + +} diff --git a/RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java new file mode 100644 index 00000000..397ea31b --- /dev/null +++ b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java @@ -0,0 +1,22 @@ +package com.hubspot.rosetta.beans; + +public class MoreFieldsInnerFieldBean { + private String firstStringProperty; + private String secondStringProperty; + + public String getFirstStringProperty() { + return firstStringProperty; + } + + public void setFirstStringProperty(String firstStringProperty) { + this.firstStringProperty = firstStringProperty; + } + + public String getSecondStringProperty() { + return secondStringProperty; + } + + public void setSecondStringProperty(String secondStringProperty) { + this.secondStringProperty = secondStringProperty; + } +} diff --git a/RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java new file mode 100644 index 00000000..84573211 --- /dev/null +++ b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java @@ -0,0 +1,30 @@ +package com.hubspot.rosetta.beans; + +import java.util.Optional; + +import com.hubspot.rosetta.annotations.NestedOptional; + +public class NestedOptionalBean { + + @NestedOptional + private Optional firstNestedOptional; + + @NestedOptional + private Optional secondNestedOptional; + + public Optional getFirstNestedOptional() { + return firstNestedOptional; + } + + public void setFirstNestedOptional(Optional firstNestedOptional) { + this.firstNestedOptional = firstNestedOptional; + } + + public Optional getSecondNestedOptional() { + return secondNestedOptional; + } + + public void setSecondNestedOptional(Optional secondNestedOptional) { + this.secondNestedOptional = secondNestedOptional; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java index d780d51b..56bbe0bb 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java @@ -15,8 +15,12 @@ public void setup() { jdbi.useHandle(handle -> { handle.execute("CREATE TABLE IF NOT EXISTS test_table (id INT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id))"); handle.execute("CREATE TABLE IF NOT EXISTS test_list_table (id INT, \"value\" INT NOT NULL, PRIMARY KEY (id))"); + handle.execute("CREATE TABLE IF NOT EXISTS test_nested_table (id INT, relatedId INT, otherName VARCHAR(255), score BIGINT, PRIMARY KEY (id))"); + handle.execute("CREATE TABLE IF NOT EXISTS test_subtyped_nested_table (relatedId INT, color VARCHAR(255), relaxSong VARCHAR(255) DEFAULT NULL, relaxLevel BIGINT DEFAULT NULL, dangerLevel BIGINT DEFAULT NULL, PRIMARY KEY (relatedId))"); handle.execute("TRUNCATE TABLE test_table"); handle.execute("TRUNCATE TABLE test_list_table"); + handle.execute("TRUNCATE TABLE test_nested_table"); + handle.execute("TRUNCATE TABLE test_subtyped_nested_table"); }); } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java new file mode 100644 index 00000000..fe822497 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java @@ -0,0 +1,54 @@ +package com.hubspot.rosetta.jdbi3; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.junit.Test; + +public class NestedOptionalTest extends AbstractJdbiTest { + + @Test + public void itDeserializesNestedObject() { + TestObject firstObject = new TestObject(1, "name-1"); + TestRelatedObject relatedObject = new TestRelatedObject(1, 1, "related-name-1", 12); + + TestViewObject firstExpectedView = new TestViewObject(1, "name-1", Optional.of(relatedObject)); + + TestObject secondObject = new TestObject(2, "name-2"); + + TestViewObject secondExpectedView = new TestViewObject(2, "name-2", Optional.empty()); + + getDao().insert(firstObject); + getDao().insert(relatedObject); + + getDao().insert(secondObject); + + assertThat(getDao().getAllView()).contains(firstExpectedView, secondExpectedView); + } + + @Test + public void itDeserializedSubTypedNestedObject() { + TestObject firstObject = new TestObject(1, "name-1"); + TestGreenNestedObject firstRelatedObject = new TestGreenNestedObject(1, 1000L, "relax-song-1"); + + TestObject secondObject = new TestObject(2, "name-2"); + TestRedNestedObject secondRelatedObject = new TestRedNestedObject(2, 300L); + + TestObject thirdObject = new TestObject(3, "name-3"); + + TestSubTypedViewObject firstExpectedView = new TestSubTypedViewObject(1, "name-1", Optional.of(firstRelatedObject)); + TestSubTypedViewObject secondExpectedView = new TestSubTypedViewObject(2, "name-2", Optional.of(secondRelatedObject)); + TestSubTypedViewObject thirdExpectedView = new TestSubTypedViewObject(3, "name-3", Optional.empty()); + + getDao().insert(firstObject); + getDao().insert(firstRelatedObject); + + getDao().insert(secondObject); + getDao().insert(secondRelatedObject); + + getDao().insert(thirdObject); + + assertThat(getDao().getAllSubTypedNestedView()).contains(firstExpectedView, secondExpectedView, thirdExpectedView); + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java index 79573255..28bf428b 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java @@ -35,9 +35,41 @@ public interface TestDao extends SqlObject { @SqlQuery("SELECT * FROM test_list_table WHERE \"value\" IN ()") List getWithObjectFieldValue(@BindListWithRosetta(value = "values", field = "objectValue") List values); + @SqlQuery("SELECT " + + "test_table.id AS id, " + + "test_table.name AS name, " + + "r.id AS \"related.id\", " + + "r.relatedId AS \"related.relatedId\", " + + "r.otherName AS \"related.otherName\", " + + "r.score AS \"related.score\" " + + "FROM test_table LEFT JOIN test_nested_table as r " + + "ON test_table.id = r.relatedId;") + List getAllView(); + + @SqlQuery("SELECT " + + "test_table.id AS id, " + + "test_table.name AS name, " + + "subtyped.relatedId AS \"related.relatedId\", " + + "subtyped.color AS \"related.color\", " + + "subtyped.dangerLevel AS \"related.dangerLevel\", " + + "subtyped.relaxSong AS \"related.relaxSong\", " + + "subtyped.relaxLevel AS \"related.relaxLevel\" " + + "FROM test_table LEFT JOIN test_subtyped_nested_table as subtyped " + + "ON test_table.id = subtyped.relatedId;") + List getAllSubTypedNestedView(); + @SqlUpdate("INSERT INTO test_table (id, name) VALUES (:id, :name)") int insert(@BindWithRosetta TestObject object); @SqlUpdate("INSERT INTO test_list_table (id, \"value\") VALUES (:id, :value)") int insert(@BindWithRosetta TestListObject object); + + @SqlUpdate("INSERT INTO test_nested_table (id, relatedId, otherName, score) VALUES (:id, :relatedId, :otherName, :score);") + int insert(@BindWithRosetta TestRelatedObject object); + + @SqlUpdate("INSERT INTO test_subtyped_nested_table (color, relatedId, dangerLevel) VALUES (:color, :relatedId, :dangerLevel);") + int insert(@BindWithRosetta TestRedNestedObject object); + + @SqlUpdate("INSERT INTO test_subtyped_nested_table (color, relatedId, relaxSong, relaxLevel) VALUES (:color, :relatedId, :relaxSong, :relaxLevel);") + int insert(@BindWithRosetta TestGreenNestedObject object); } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java new file mode 100644 index 00000000..72138e29 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java @@ -0,0 +1,60 @@ +package com.hubspot.rosetta.jdbi3; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; + +public class TestGreenNestedObject implements TestSubTypedNestedObject { + + private final int relatedId; + private final long relaxLevel; + private final String relaxSong; + + @JsonCreator + public TestGreenNestedObject(@JsonProperty("relatedId") int relatedId, + @JsonProperty("relaxLevel") long relaxLevel, + @JsonProperty("relaxSong") String relaxSong) { + this.relatedId = relatedId; + this.relaxLevel = relaxLevel; + this.relaxSong = relaxSong; + } + + public int getRelatedId() { + return relatedId; + } + + public long getRelaxLevel() { + return relaxLevel; + } + + public String getRelaxSong() { + return relaxSong; + } + + @Override + public String getColor() { + return "GREEN"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestGreenNestedObject that = (TestGreenNestedObject) o; + return relatedId == that.relatedId && relaxLevel == that.relaxLevel && Objects.equal(relaxSong, that.relaxSong); + } + + @Override + public int hashCode() { + return Objects.hashCode(relatedId, relaxLevel, relaxSong); + } + + @Override + public String toString() { + return "TestGreenNestedObject{" + + "relatedId=" + relatedId + + ", relaxLevel=" + relaxLevel + + ", relaxSong='" + relaxSong + '\'' + + '}'; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java new file mode 100644 index 00000000..85698596 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java @@ -0,0 +1,49 @@ +package com.hubspot.rosetta.jdbi3; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; + +public class TestRedNestedObject implements TestSubTypedNestedObject { + + private final int relatedId; + private final long dangerLevel; + + public TestRedNestedObject(@JsonProperty("relatedId") int relatedId, @JsonProperty("dangerLevel") long dangerLevel) { + this.relatedId = relatedId; + this.dangerLevel = dangerLevel; + } + + public int getRelatedId() { + return relatedId; + } + + public long getDangerLevel() { + return dangerLevel; + } + + @Override + public String getColor() { + return "RED"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestRedNestedObject that = (TestRedNestedObject) o; + return relatedId == that.relatedId && dangerLevel == that.dangerLevel; + } + + @Override + public int hashCode() { + return Objects.hashCode(relatedId, dangerLevel); + } + + @Override + public String toString() { + return "TestRedNestedObject{" + + "relatedId=" + relatedId + + ", dangerLevel=" + dangerLevel + + '}'; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java new file mode 100644 index 00000000..a8efc673 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java @@ -0,0 +1,60 @@ +package com.hubspot.rosetta.jdbi3; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; + +public class TestRelatedObject { + private final int id; + private final int relatedId; + private final String otherName; + private final long score; + + @JsonCreator + public TestRelatedObject(@JsonProperty("id") int id, @JsonProperty("relatedId") int relatedId, @JsonProperty("otherName") String otherName, @JsonProperty("score") long score) { + this.id = id; + this.relatedId = relatedId; + this.otherName = otherName; + this.score = score; + } + + public int getId() { + return id; + } + + public int getRelatedId() { + return relatedId; + } + + public String getOtherName() { + return otherName; + } + + public long getScore() { + return score; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestRelatedObject that = (TestRelatedObject) o; + return id == that.id && relatedId == that.relatedId && score == that.score && Objects.equal(otherName, that.otherName); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, relatedId, otherName, score); + } + + @Override + public String toString() { + return "TestRelatedObject{" + + "id=" + id + + ", relatedId=" + relatedId + + ", name='" + otherName + '\'' + + ", score=" + score + + '}'; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java new file mode 100644 index 00000000..7dbe4e17 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java @@ -0,0 +1,23 @@ +package com.hubspot.rosetta.jdbi3; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = As.EXISTING_PROPERTY, + property = "color", + visible = true +) +@JsonSubTypes( + { + @Type(value = TestGreenNestedObject.class, name = "GREEN"), + @Type(value = TestRedNestedObject.class, name = "RED"), + } +) +public interface TestSubTypedNestedObject { + int getRelatedId(); + String getColor(); +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java new file mode 100644 index 00000000..0b59c879 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java @@ -0,0 +1,57 @@ +package com.hubspot.rosetta.jdbi3; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; +import com.hubspot.rosetta.annotations.NestedOptional; + +public class TestSubTypedViewObject { + private final int id; + private final String name; + + @NestedOptional + private final Optional related; + + @JsonCreator + public TestSubTypedViewObject(@JsonProperty("id") int id, @JsonProperty("name") String name, @JsonProperty("related") Optional related) { + this.id = id; + this.name = name; + this.related = related; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public Optional getRelated() { + return related; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestSubTypedViewObject that = (TestSubTypedViewObject) o; + return id == that.id && Objects.equal(name, that.name) && Objects.equal(related, that.related); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, name, related); + } + + @Override + public String toString() { + return "TestSubTypedViewObject{" + + "id=" + id + + ", name='" + name + '\'' + + ", related=" + related + + '}'; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java new file mode 100644 index 00000000..8503f765 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java @@ -0,0 +1,59 @@ +package com.hubspot.rosetta.jdbi3; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.hubspot.rosetta.annotations.NestedOptional; + +public class TestViewObject { + + private final int id; + private final String name; + + @NestedOptional + private final Optional related; + + @JsonCreator + public TestViewObject(@JsonProperty("id") int id, @JsonProperty("name") String name, @JsonProperty("related") Optional related) { + this.id = id; + this.name = name; + this.related = Preconditions.checkNotNull(related); + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public Optional getRelated() { + return related; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestViewObject that = (TestViewObject) o; + return id == that.id && Objects.equal(name, that.name) && Objects.equal(related, that.related); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, name, related); + } + + @Override + public String toString() { + return "TestViewObject{" + + "id=" + id + + ", name='" + name + '\'' + + ", related=" + related + + '}'; + } +}