Skip to content

Instantly share code, notes, and snippets.

@ctrimble
Last active January 12, 2017 10:47
Show Gist options
  • Save ctrimble/a5d06376c1e716f1ca26 to your computer and use it in GitHub Desktop.
Save ctrimble/a5d06376c1e716f1ca26 to your computer and use it in GitHub Desktop.
Implementing Json Schema oneOf using nested Jackson deserializers.
{
"children": [
{
"name": "type a",
"description": "description a",
"a": "a value"
},
{
"name": "type b",
"description": "description b",
"b": "b value"
}
]
}
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.MatcherAssert.*;
public class BasicOneOfTest {
public static interface Named {
public String getName();
}
public static interface Described {
public String getDescription();
}
public static class A implements Named, Described {
private String name;
private String description;
private String a;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getA() { return a; }
public void setA(String a) { this.a = a; }
}
public static class B implements Named, Described {
private String name;
private String description;
private String b;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getB() { return b; }
public void setB(String b) { this.b = b; }
}
public static class C {
//
// Deserializers like this would be generated for each property that contains a oneOf
//
public static class Children$Deserializer extends JsonDeserializer<Object> {
@Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// lots of details skipped here.
ObjectCodec codec = jp.getCodec();
TreeNode tree = jp.readValueAsTree();
if( !tree.isArray() ) throw new IOException("invalid input");
List<Object> value = new ArrayList<Object>();
for( int i = 0;; i++ ) {
TreeNode element = tree.get(i);
if( element == null ) break;
if( element.get("a") != null ) {
value.add(codec.readValue(element.traverse(), A.class));
}
else if( element.get("b") != null ) {
value.add(codec.readValue(element.traverse(), B.class));
}
else {
throw new IOException("invalid input");
}
}
return value;
}
}
//
// Fields that contain oneOf types would use the internally generated deserializer.
//
@JsonDeserialize(using=Children$Deserializer.class)
public List<?> children;
//
// Interfaces common to all objects in a oneOf would be exposed on the getters and setters.
//
@SuppressWarnings("unchecked")
public <T extends Named & Described> List<T> getChildren() { return (List<T>) children; }
}
@SuppressWarnings("unchecked")
@Test
public void deserializeC() throws Exception {
ObjectMapper mapper = new ObjectMapper();
try( InputStream in = this.getClass().getResourceAsStream("./basicOneOf.json")) {
C c = mapper.readValue(in, C.class);
// correct types
assertThat(c, hasProperty("children", contains(
allOf(
instanceOf(A.class),
hasProperty("name", equalTo("type a")),
hasProperty("description", equalTo("description a")),
hasProperty("a", equalTo("a value"))
),
allOf(
instanceOf(B.class),
hasProperty("name", equalTo("type b")),
hasProperty("description", equalTo("description b")),
hasProperty("b", equalTo("b value"))
)
)));
// access generically
assertThat(c.getChildren().get(0).getName(), equalTo("type a"));
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment