package com.fasterxml.jackson.databind.jsonschema; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonParser.NumberType; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.jsonFormatVisitors.*; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.ser.std.StdSerializer; /** * Basic tests to exercise low-level support added for JSON Schema module and * other modules that use type introspection. */ public class NewSchemaTest extends BaseMapTest { enum TestEnum { A, B, C; @Override public String toString() { return "ToString:"+name(); } } enum TestEnumWithJsonValue { A, B, C; @JsonValue public String forSerialize() { return "value-"+name(); } } // silly little class to exercise basic traversal static class POJO { public List<POJO> children; public POJO[] childOrdering; public Map<String, java.util.Date> times; public Map<String,Integer> conversions; public EnumMap<TestEnum,Double> weights; } static class POJOWithScalars { public boolean boo; public byte b; public char c; public short s; public int i; public long l; public float f; public double d; public byte[] arrayBoo; public byte[] arrayb; public char[] arrayc; public short[] arrays; public int[] arrayi; public long[] arrayl; public float[] arrayf; public double[] arrayd; public Boolean Boo; public Byte B; public Character C; public Short S; public Integer I; public Long L; public Float F; public Double D; public TestEnum en; public String str; public String[] strs; public java.util.Date date; public java.util.Calendar calendar; } static class POJOWithRefs { public AtomicReference<POJO> maybePOJO; public AtomicReference<String> maybeString; } @JsonPropertyOrder({ "dec", "bigInt" }) static class Numbers { public BigDecimal dec; public BigInteger bigInt; } static class BogusJsonFormatVisitorWrapper extends JsonFormatVisitorWrapper.Base { // Implement handlers just to get more exercise... @Override public JsonObjectFormatVisitor expectObjectFormat(JavaType type) { return new JsonObjectFormatVisitor.Base(getProvider()) { @Override public void property(BeanProperty prop) throws JsonMappingException { _visit(prop); } @Override public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) { } @Override public void optionalProperty(BeanProperty prop) throws JsonMappingException { _visit(prop); } @Override public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException { } private void _visit(BeanProperty prop) throws JsonMappingException { if (!(prop instanceof BeanPropertyWriter)) { return; } BeanPropertyWriter bpw = (BeanPropertyWriter) prop; JsonSerializer<?> ser = bpw.getSerializer(); final SerializerProvider prov = getProvider(); if (ser == null) { if (prov == null) { throw new Error("SerializerProvider missing"); } ser = prov.findValueSerializer(prop.getType(), prop); } // and this just for bit of extra coverage... if (ser instanceof StdSerializer) { assertNotNull(((StdSerializer<?>) ser).getSchema(prov, prop.getType())); } JsonFormatVisitorWrapper visitor = new JsonFormatVisitorWrapper.Base(getProvider()); ser.acceptJsonFormatVisitor(visitor, prop.getType()); } }; } @Override public JsonArrayFormatVisitor expectArrayFormat(JavaType type) { return new JsonArrayFormatVisitor.Base(getProvider()); } @Override public JsonStringFormatVisitor expectStringFormat(JavaType type) { return new JsonStringFormatVisitor.Base(); } @Override public JsonNumberFormatVisitor expectNumberFormat(JavaType type) { return new JsonNumberFormatVisitor.Base(); } @Override public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) { return new JsonIntegerFormatVisitor.Base(); } @Override public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) { return new JsonBooleanFormatVisitor.Base(); } @Override public JsonNullFormatVisitor expectNullFormat(JavaType type) { return new JsonNullFormatVisitor.Base(); } @Override public JsonAnyFormatVisitor expectAnyFormat(JavaType type) { return new JsonAnyFormatVisitor.Base(); } @Override public JsonMapFormatVisitor expectMapFormat(JavaType type) { return new JsonMapFormatVisitor.Base(); } } /* /********************************************************** /* Test methods /********************************************************** */ private final ObjectMapper MAPPER = new ObjectMapper(); /* Silly little test for simply triggering traversal, without attempting to * verify what is being reported. Smoke test that should trigger problems * if basic POJO type/serializer traversal had issues. */ public void testBasicTraversal() throws Exception { MAPPER.acceptJsonFormatVisitor(POJO.class, new BogusJsonFormatVisitorWrapper()); MAPPER.acceptJsonFormatVisitor(POJOWithScalars.class, new BogusJsonFormatVisitorWrapper()); MAPPER.acceptJsonFormatVisitor(LinkedHashMap.class, new BogusJsonFormatVisitorWrapper()); MAPPER.acceptJsonFormatVisitor(ArrayList.class, new BogusJsonFormatVisitorWrapper()); MAPPER.acceptJsonFormatVisitor(EnumSet.class, new BogusJsonFormatVisitorWrapper()); MAPPER.acceptJsonFormatVisitor(POJOWithRefs.class, new BogusJsonFormatVisitorWrapper()); } public void testSimpleEnum() throws Exception { final Set<String> values = new TreeSet<String>(); ObjectWriter w = MAPPER.writer(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); w.acceptJsonFormatVisitor(TestEnum.class, new JsonFormatVisitorWrapper.Base() { @Override public JsonStringFormatVisitor expectStringFormat(JavaType type) { return new JsonStringFormatVisitor() { @Override public void enumTypes(Set<String> enums) { values.addAll(enums); } @Override public void format(JsonValueFormat format) { } }; } }); assertEquals(3, values.size()); TreeSet<String> exp = new TreeSet<String>(Arrays.asList( "ToString:A", "ToString:B", "ToString:C" )); assertEquals(exp, values); } public void testEnumWithJsonValue() throws Exception { final Set<String> values = new TreeSet<String>(); MAPPER.acceptJsonFormatVisitor(TestEnumWithJsonValue.class, new JsonFormatVisitorWrapper.Base() { @Override public JsonStringFormatVisitor expectStringFormat(JavaType type) { return new JsonStringFormatVisitor() { @Override public void enumTypes(Set<String> enums) { values.addAll(enums); } @Override public void format(JsonValueFormat format) { } }; } }); assertEquals(3, values.size()); TreeSet<String> exp = new TreeSet<String>(Arrays.asList( "value-A", "value-B", "value-C" )); assertEquals(exp, values); } // Ensure JsonValueFormat serializes/deserializes as expected public void testJsonValueFormatHandling() throws Exception { // first: serialize using 'toString()', not name final String EXP = quote("host-name"); assertEquals(EXP, MAPPER.writeValueAsString(JsonValueFormat.HOST_NAME)); // and second, deserialize ok from that as well assertSame(JsonValueFormat.HOST_NAME, MAPPER.readValue(EXP, JsonValueFormat.class)); } // [databind#1045], regression wrt BigDecimal public void testSimpleNumbers() throws Exception { final StringBuilder sb = new StringBuilder(); MAPPER.acceptJsonFormatVisitor(Numbers.class, new JsonFormatVisitorWrapper.Base() { @Override public JsonObjectFormatVisitor expectObjectFormat(final JavaType type) { return new JsonObjectFormatVisitor.Base(getProvider()) { @Override public void optionalProperty(BeanProperty prop) throws JsonMappingException { sb.append("[optProp ").append(prop.getName()).append("("); JsonSerializer<Object> ser = null; if (prop instanceof BeanPropertyWriter) { BeanPropertyWriter bpw = (BeanPropertyWriter) prop; ser = bpw.getSerializer(); } final SerializerProvider prov = getProvider(); if (ser == null) { ser = prov.findValueSerializer(prop.getType(), prop); } ser.acceptJsonFormatVisitor(new JsonFormatVisitorWrapper.Base() { @Override public JsonNumberFormatVisitor expectNumberFormat( JavaType t) throws JsonMappingException { return new JsonNumberFormatVisitor() { @Override public void format(JsonValueFormat format) { sb.append("[numberFormat=").append(format).append("]"); } @Override public void enumTypes(Set<String> enums) { } @Override public void numberType(NumberType numberType) { sb.append("[numberType=").append(numberType).append("]"); } }; } @Override public JsonIntegerFormatVisitor expectIntegerFormat(JavaType t) throws JsonMappingException { return new JsonIntegerFormatVisitor() { @Override public void format(JsonValueFormat format) { sb.append("[integerFormat=").append(format).append("]"); } @Override public void enumTypes(Set<String> enums) { } @Override public void numberType(NumberType numberType) { sb.append("[numberType=").append(numberType).append("]"); } }; } }, prop.getType()); sb.append(")]"); } }; } }); assertEquals("[optProp dec([numberType=BIG_DECIMAL])][optProp bigInt([numberType=BIG_INTEGER])]", sb.toString()); } }