diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalBigDecimalToString.java b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalBigDecimalToString.java new file mode 100644 index 0000000000..3ebf1f0c23 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalBigDecimalToString.java @@ -0,0 +1,43 @@ +package com.fasterxml.jackson.databind.ser; + +import java.math.BigDecimal; + +public class CanonicalBigDecimalToString implements ValueToString { + + public static final CanonicalBigDecimalToString INSTANCE = new CanonicalBigDecimalToString(); + + @Override + public String convert(BigDecimal value) { + BigDecimal stripped = value.stripTrailingZeros(); + int scale = stripped.scale(); + String text = stripped.toPlainString(); + if (scale == 0) { + return text; + } + + int pos = text.indexOf('.'); + int exp; + if (pos >= 0) { + exp = pos - 1; + + if (exp == 0) { + return text; + } + + text = text.substring(0, pos) + text.substring(pos + 1); + } else { + exp = -scale; + int end = text.length(); + while (end > 0 && text.charAt(end - 1) == '0') { + end --; + } + text = text.substring(0, end); + } + + if (text.length() == 1) { + return text + 'E' + exp; + } + + return text.substring(0, 1) + '.' + text.substring(1) + 'E' + exp; + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalBigDecimalToStringTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalBigDecimalToStringTest.java new file mode 100644 index 0000000000..74ed452297 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalBigDecimalToStringTest.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.databind.ser; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; + +public class CanonicalBigDecimalToStringTest { + + @Test + void testCanonicalDecimalHandling_1() throws Exception { + assertSerialized("1", new BigDecimal("1")); + } + + @Test + void testCanonicalDecimalHandling_1_000() throws Exception { + assertSerialized("1", new BigDecimal("1.000")); + } + + @Test + void testCanonicalDecimalHandling_10_1000() throws Exception { + assertSerialized("1.01E1", new BigDecimal("10.1000")); + } + + @Test + void testCanonicalDecimalHandling_1000() throws Exception { + assertSerialized("1E3", new BigDecimal("1000")); + } + + @Test + void testCanonicalDecimalHandling_0_00000000010() throws Exception { + assertSerialized("0.0000000001", new BigDecimal("0.00000000010")); + } + + @Test + void testCanonicalDecimalHandling_1000_00010() throws Exception { + assertSerialized("1.0000001E3", new BigDecimal("1000.00010")); + } + + @Test + void testCanonicalHugeDecimalHandling() throws Exception { + BigDecimal actual = new BigDecimal("123456789123456789123456789123456789.123456789123456789123456789123456789123456789000"); + assertSerialized("1.23456789123456789123456789123456789123456789123456789123456789123456789123456789E35", actual); + } + + private void assertSerialized(String expected, BigDecimal actual) { + CanonicalBigDecimalToString serializer = new CanonicalBigDecimalToString(); + assertEquals(expected, serializer.convert(actual)); + } + +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonGeneratorDecorator.java b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonGeneratorDecorator.java new file mode 100644 index 0000000000..4e670a1f7b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonGeneratorDecorator.java @@ -0,0 +1,21 @@ +package com.fasterxml.jackson.databind.ser; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonGeneratorDecorator; + +public class CanonicalJsonGeneratorDecorator implements JsonGeneratorDecorator { + + private ValueToString _serializer; + + public CanonicalJsonGeneratorDecorator(ValueToString serializer) { + this._serializer = serializer; + } + + @Override + public JsonGenerator decorate(JsonFactory factory, JsonGenerator generator) { + return new CanonicalNumberGenerator(generator, _serializer); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonMapper.java b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonMapper.java new file mode 100644 index 0000000000..c9329b0114 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonMapper.java @@ -0,0 +1,65 @@ +package com.fasterxml.jackson.databind.ser; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGeneratorDecorator; +import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.StreamWriteFeature; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; +import com.fasterxml.jackson.core.util.Separators.Spacing; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; + +public class CanonicalJsonMapper { + public static final DefaultIndenter CANONICAL_INDENTEER = new DefaultIndenter(" ", "\n"); + + public static final PrettyPrinter CANONICAL_PRETTY_PRINTER = new DefaultPrettyPrinter() + .withObjectIndenter(CANONICAL_INDENTEER) + .withSeparators(Separators.createDefaultInstance().withObjectFieldValueSpacing(Spacing.AFTER)); + + public static class Builder { + private ValueToString _numberToString = CanonicalBigDecimalToString.INSTANCE; + private boolean _enablePrettyPrinting = false; + + private Builder() { + // Don't allow to create except via builder method + } + + public Builder prettyPrint() { + _enablePrettyPrinting = true; + _numberToString = PrettyBigDecimalToString.INSTANCE; + return this; + } + + public JsonMapper build() { + JsonGeneratorDecorator decorator = new CanonicalJsonGeneratorDecorator(_numberToString); + + JsonFactory factory = JsonFactory.builder() // + .decorateWith(decorator) + .build(); + JsonMapper.Builder builder = JsonMapper.builder(factory) // + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) // + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) // + .enable(JsonNodeFeature.WRITE_PROPERTIES_SORTED); // + + if (_enablePrettyPrinting) { + builder = builder // + .enable(SerializationFeature.INDENT_OUTPUT) // + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) // + .defaultPrettyPrinter(CANONICAL_PRETTY_PRINTER) // + ; + } + + return builder.build(); + } + } + + public static CanonicalJsonMapper.Builder builder() { + return new Builder(); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonTest.java new file mode 100644 index 0000000000..9766ba304b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalJsonTest.java @@ -0,0 +1,289 @@ +package com.fasterxml.jackson.databind.ser; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; + +class CanonicalJsonTest { + + private static final BigDecimal TEN_POINT_1_WITH_TRAILING_ZEROES = new BigDecimal("10.1000"); + private static final BigDecimal VERY_BIG_DECIMAL = new BigDecimal("123456789123456789123456789123456789.123456789123456789123456789123456789123456789000"); + private static final double NEGATIVE_ZERO = -0.; + private static final JsonAssert JSON_ASSERT = new JsonAssert(); + private static final JsonTestResource CANONICAL_1 = new JsonTestResource("/data/canonical-1.json"); + + @Test + void testSignOfNegativeZero() { + assertEquals("-0.0", Double.toString(NEGATIVE_ZERO)); + } + + @Test + void testNegativeZeroIsEqualToZero() { + assertEquals(0.0, NEGATIVE_ZERO, 1e-9); + } + + @Test + void testCanonicalBigDecimalSerializationTrailingZeros() throws Exception { + assertSerialized("1", new BigDecimal("1.0000"), newCanonicalMapperBuilder()); + } + + @Test + void testCanonicalNegativeZeroBigDecimal() throws Exception { + assertSerialized("0", new BigDecimal("-0"), newCanonicalMapperBuilder()); + } + + @Test + void testCanonicalNegativeZeroBigDecimal2() throws Exception { + assertSerialized("0", new BigDecimal(NEGATIVE_ZERO), newCanonicalMapperBuilder()); + } + + @Test + void testCanonicalNegativeZeroBigDecimal3() throws Exception { + assertSerialized("0", BigDecimal.valueOf(NEGATIVE_ZERO), newCanonicalMapperBuilder()); + } + + @Test + void testCanonicalNegativeZeroDouble() throws Exception { + assertSerialized("0", NEGATIVE_ZERO, newCanonicalMapperBuilder()); + } + + @Test + void testCanonicalDecimalHandling() throws Exception { + assertSerialized("1.01E1", TEN_POINT_1_WITH_TRAILING_ZEROES, newCanonicalMapperBuilder()); + } + + @Test + void testCanonicalHugeDecimalHandling() throws Exception { + assertSerialized("1.23456789123456789123456789123456789123456789123456789123456789123456789123456789E35", VERY_BIG_DECIMAL, newCanonicalMapperBuilder()); + } + + @Test + void testPrettyDecimalHandling() throws Exception { + JSON_ASSERT.assertSerialized("10.1", TEN_POINT_1_WITH_TRAILING_ZEROES); + } + + @Test + void testPrettyHugeDecimalHandling() throws Exception { + JSON_ASSERT.assertSerialized("123456789123456789123456789123456789.123456789123456789123456789123456789123456789", VERY_BIG_DECIMAL); + } + + @Test + void testCanonicalJsonSerialization() throws Exception { + JsonNode expected = JSON_ASSERT.loadResource(CANONICAL_1); + JsonNode actual = buildTestData(); + + assertCanonicalJson(expected, actual); + } + + @Test + void testCanonicalJsonSerializationRandomizedChildren() throws Exception { + JsonNode expected = JSON_ASSERT.loadResource(CANONICAL_1); + JsonNode actual = randomize(buildTestData()); + + assertCanonicalJson(expected, actual); + } + + @Test + void testPrettyJsonSerialization() throws Exception { + JsonNode actual = buildTestData(); + + JSON_ASSERT.assertJson(CANONICAL_1, actual); + } + + @Test + void testPrettyJsonSerializationRandomizedChildren() throws Exception { + JsonNode actual = randomize(buildTestData()); + + JSON_ASSERT.assertJson(CANONICAL_1, actual); + } + + @Test + void testJsonTypeInfoSorting() throws Exception { + Impl1 inst = new Impl1(); + inst.setValue(97); + + JSON_ASSERT.assertStableSerialization( + "{\n" + + " \"type\": \"i1\",\n" // TODO this property should be after name + + " \"name\": \"Impl1\",\n" + + " \"value\": 97\n" + + "}", + inst, + TypeBase.class); + } + + @Test + void testJsonTypeInfoSorting2() throws Exception { + Impl2 inst = new Impl2(); + inst.setDecimal(3.1415); + + JSON_ASSERT.assertStableSerialization( + "{\n" + + " \"type\": \"i2\",\n" // TODO this property should be after name + + " \"decimal\": 3.1415,\n" + + " \"name\": \"Impl2\"\n" + + "}", + inst, + TypeBase.class); + } + + @Test + void testBigDecimalValue() throws Exception { + BigDecimalValue inst = new BigDecimalValue(); + + inst.setValue(BigDecimal.valueOf(0.1)); + assertEquals("0.1", inst.getValue().toString()); + JSON_ASSERT.assertStableSerialization( + "{\n" + + " \"value\": 0.1\n" + + "}", + inst, + BigDecimalValue.class); + } + + @Test + void testBigDecimalValue2() throws Exception { + BigDecimalValue inst = new BigDecimalValue(); + + inst.setValue(new BigDecimal("10.0100")); + assertEquals("10.0100", inst.getValue().toString()); + JSON_ASSERT.assertStableSerialization( + "{\n" + + " \"value\": 10.01\n" + + "}", + inst, + BigDecimalValue.class); + } + + private void assertSerialized(String expected, Object input, JsonMapper mapper) { + String actual; + try { + actual = mapper.writeValueAsString(input); + } catch (JsonProcessingException e) { + throw new AssertionError("Unable to serialize " + input, e); + } + assertEquals(expected, actual); + } + + private JsonMapper newCanonicalMapperBuilder() { + return CanonicalJsonMapper.builder().build(); + } + + private JsonNode randomize(JsonNode input) { + if (input instanceof ObjectNode) { + List> copy = Lists.newArrayList(input.fields()); + Collections.shuffle(copy); + + Map randomized = new LinkedHashMap<>(); + copy.forEach(entry -> { + randomized.put(entry.getKey(), randomize(entry.getValue())); + }); + + return new ObjectNode(JsonNodeFactory.instance, randomized); + } else { + return input; + } + } + + private void assertCanonicalJson(JsonNode expected, JsonNode actual) { + ObjectMapper mapper = newCanonicalMapperBuilder(); + assertEquals(serialize(expected, mapper), serialize(actual, mapper)); + } + + private String serialize(JsonNode input, ObjectMapper mapper) { + try { + return mapper.writeValueAsString(input); + } catch (JacksonException e) { + throw new AssertionError("Unable to serialize " + input, e); + } + } + + private JsonNode buildTestData() { + return new ObjectNode(JsonNodeFactory.instance) // + .put("-0", NEGATIVE_ZERO) // + .put("-1", -1) // + .put("0.1", new BigDecimal("0.100")) // + .put("1", new BigDecimal("1")) // + .put("10.1", TEN_POINT_1_WITH_TRAILING_ZEROES) // + .put("emoji", "\uD83D\uDE03") // + .put("escape", "\u001B") // + .put("lone surrogate", "\uDEAD") // + .put("whitespace", " \t\n\r") // + ; + } + + @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type") + @JsonSubTypes({ + @JsonSubTypes.Type(value=Impl1.class, name="i1"), + @JsonSubTypes.Type(value=Impl2.class, name="i2") + }) + public interface TypeBase { + String getName(); + } + + public static class Impl1 implements TypeBase { + private String name = "Impl1"; + private int value; + + @Override + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getValue() { + return value; + } + public void setValue(int value) { + this.value = value; + } + } + + public static class Impl2 implements TypeBase { + private String name = "Impl2"; + private double decimal; + + @Override + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public double getDecimal() { + return decimal; + } + public void setDecimal(double decimal) { + this.decimal = decimal; + } + } + + public static class BigDecimalValue { + private BigDecimal value; + + public BigDecimal getValue() { + return value; + } + public void setValue(BigDecimal value) { + this.value = value; + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalNumberGenerator.java b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalNumberGenerator.java new file mode 100644 index 0000000000..116efd944c --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/CanonicalNumberGenerator.java @@ -0,0 +1,66 @@ +package com.fasterxml.jackson.databind.ser; + +import java.io.IOException; +import java.math.BigDecimal; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.util.JsonGeneratorDelegate; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class CanonicalNumberGenerator extends JsonGeneratorDelegate { + + /** + * TODO The constant should be public or the verify method. Copied from + * `jackson-databing` class `NumberSerializer` + */ + protected final static int MAX_BIG_DECIMAL_SCALE = 9999; + + private final ValueToString _bigDecimalToString; + + public CanonicalNumberGenerator(JsonGenerator gen, ValueToString bigDecimalToString) { + super(gen); + this._bigDecimalToString = bigDecimalToString; + } + + @Override + public void writeNumber(double v) throws IOException { + BigDecimal wrapper = BigDecimal.valueOf(v); + writeNumber(wrapper); + } + + @Override + public void writeNumber(BigDecimal v) throws IOException { + if (!verifyBigDecimalRange(v)) { + String msg = bigDecimalOutOfRangeError(v); + throw new JsonGenerationException(msg, this); + } + + String converted = _bigDecimalToString.convert(v); + delegate.writeNumber(converted); + } + + public static boolean verifyBigDecimalRange(BigDecimal value, SerializerProvider provider) throws JsonMappingException { + boolean result = verifyBigDecimalRange(value); + + if (!result) { + provider.reportMappingProblem(bigDecimalOutOfRangeError(value)); + } + + return result; + } + + // TODO Everyone should use the same method; move this to jackson-core. + public static boolean verifyBigDecimalRange(BigDecimal value) { + int scale = value.scale(); + return ((scale >= -MAX_BIG_DECIMAL_SCALE) && (scale <= MAX_BIG_DECIMAL_SCALE)); + } + + // TODO Everyone should use the same method; move this to jackson-core. + public static String bigDecimalOutOfRangeError(BigDecimal value) { + return String.format( + "Attempt to write plain `java.math.BigDecimal` (see StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) with illegal scale (%d): needs to be between [-%d, %d]", + value.scale(), MAX_BIG_DECIMAL_SCALE, MAX_BIG_DECIMAL_SCALE); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/JsonAssert.java b/src/test/java/com/fasterxml/jackson/databind/ser/JsonAssert.java new file mode 100644 index 0000000000..fa9fdefc5b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/JsonAssert.java @@ -0,0 +1,79 @@ +package com.fasterxml.jackson.databind.ser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.io.InputStream; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.json.JsonMapper; + +public class JsonAssert { + + private JsonMapper _mapper; // TODO If we don't want to allow people to configure this, then everything in here can be static which would make it look a lot like JUnit Assertions. + + public JsonAssert() { + _mapper = CanonicalJsonMapper.builder() // + .prettyPrint() // Always pretty print to make the diff in failed tests easier to use + .build(); + } + + /** Make sure the object will be serialized into the expected JSON */ + public void assertJson(JsonTestResource expected, Object actual) { + assertJson(loadResource(expected), actual); + } + + /** Make sure the object will be serialized into the expected JSON */ + public void assertJson(JsonNode expected, Object actual) { + assertEquals(serialize(expected), serialize(actual)); + } + + /** Make sure the object will be serialized into the expected JSON */ + public void assertSerialized(String expectedJson, Object actual) { + assertEquals(expectedJson, serialize(actual)); + } + + /** Make sure the object will be serialized into the expected JSON and that it can be deserialized. */ + public void assertStableSerialization(String expectedJson, Object actual, Class baseType) throws IOException { + assertEquals(expectedJson, serialize(actual)); + + Object deserialized = _mapper.readValue(expectedJson, baseType); + assertEquals(expectedJson, serialize(deserialized)); + } + + /** Serialize a JsonNode tree */ + public String serialize(JsonNode input) { + try { + return _mapper.writeValueAsString(input); + } catch(JacksonException e) { + throw new AssertionError("Serializing failed: " + input, e); + } + } + + /** Serialize any object. This even works when data is a {@link JsonNode}. */ + public String serialize(Object data) { + if (data instanceof JsonNode) { + return serialize((JsonNode)data); + } + + // TODO Sorting mostly works except for properties defined by @JsonTypeInfo + try { + return _mapper.writeValueAsString(data); + } catch(JacksonException e) { + throw new AssertionError("Serializing failed: " + data, e); + } + } + + /** Load test data from the classpath into a {@link JsonNode}. */ + public JsonNode loadResource(JsonTestResource resource) { + try (InputStream stream = resource.getInputStream()) { + assertNotNull("Missing resource " + resource, stream); + + return _mapper.readTree(stream); + } catch (IOException e) { + throw new AssertionError("Error loading " + resource, e); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/JsonTestResource.java b/src/test/java/com/fasterxml/jackson/databind/ser/JsonTestResource.java new file mode 100644 index 0000000000..76b3eed84e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/JsonTestResource.java @@ -0,0 +1,47 @@ +package com.fasterxml.jackson.databind.ser; + +import java.io.InputStream; +import java.net.URL; + +public class JsonTestResource { + + private String resourceName; + private ClassLoader classLoader; + + public JsonTestResource(String resourceName) { + this(resourceName, JsonTestResource.class.getClassLoader()); + } + + public JsonTestResource(String resourceName, ClassLoader classLoader) { + this.resourceName = fix(resourceName); + this.classLoader = classLoader; + } + + private static String fix(String resourceName) { + if (resourceName.startsWith("/")) { + return resourceName.substring(1); + } + return resourceName; + } + + public URL getURL() { + return classLoader.getResource(resourceName); + } + + public InputStream getInputStream() { + return classLoader.getResourceAsStream(resourceName); + } + + @Override + public String toString() { + URL url = getURL(); + String content; + if (url == null) { + content = "resourceName=" + resourceName + " (not on classpath)"; + } else { + content = "url=" + url; + } + + return getClass().getSimpleName() + "(" + content + ")"; + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/PrettyBigDecimalToString.java b/src/test/java/com/fasterxml/jackson/databind/ser/PrettyBigDecimalToString.java new file mode 100644 index 0000000000..23edb1a5ae --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/PrettyBigDecimalToString.java @@ -0,0 +1,13 @@ +package com.fasterxml.jackson.databind.ser; + +import java.math.BigDecimal; + +public class PrettyBigDecimalToString implements ValueToString { + + public static final PrettyBigDecimalToString INSTANCE = new PrettyBigDecimalToString(); + + @Override + public String convert(BigDecimal value) { + return value.stripTrailingZeros().toPlainString(); + } +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/ValueToString.java b/src/test/java/com/fasterxml/jackson/databind/ser/ValueToString.java new file mode 100644 index 0000000000..0349427835 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/ValueToString.java @@ -0,0 +1,5 @@ +package com.fasterxml.jackson.databind.ser; + +public interface ValueToString { + String convert(T value); +} diff --git a/src/test/resources/data/canonical-1.json b/src/test/resources/data/canonical-1.json new file mode 100644 index 0000000000..a9b62c644a --- /dev/null +++ b/src/test/resources/data/canonical-1.json @@ -0,0 +1 @@ +{"-0":0,"-1":-1,"0.1":1.0E-1,"1":1,"10.1":1.01E1,"emoji":"😃","escape":"\u001B","lone surrogate":"\uDEAD","whitespace":" \t\n\r"} \ No newline at end of file