From f07bda27c16e464d4943340b17644f01d1014996 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 11 Aug 2020 20:52:10 -0700 Subject: [PATCH] Fixed #2815 (add Shape overrides for UUIDSerializer) --- release-notes/VERSION-2.x | 1 + .../databind/ser/std/UUIDSerializer.java | 50 ++++++++--- .../ser/jdk/JDKTypeSerializationTest.java | 38 +-------- .../ser/jdk/UUIDSerializationTest.java | 82 +++++++++++++++++++ 4 files changed, 124 insertions(+), 47 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/ser/jdk/UUIDSerializationTest.java diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index a8924c87c6..e6ffe9ff90 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -14,6 +14,7 @@ Project: jackson-databind (reported by isaki@github) #2796: `TypeFactory.constructType()` does not take `TypeBindings` correctly (reported by Daniel H) +#2815: Add `JsonFormat.Shape` awareness for UUID serialization (`UUIDSerializer`) 2.11.1 (25-Jun-2020) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java index c503bf4ec5..99fa46663f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java @@ -25,7 +25,24 @@ public class UUIDSerializer { final static char[] HEX_CHARS = "0123456789abcdef".toCharArray(); - public UUIDSerializer() { super(UUID.class); } + /** + * Configuration setting that indicates if serialization as binary + * (native or Base64-encoded) has been forced; {@code null} means + * "use default heuristic" + * + * @since 2.11.3 + */ + protected final Boolean _asBinary; + + public UUIDSerializer() { this(null); } + + /** + * @since 2.11.3 + */ + protected UUIDSerializer(Boolean asBinary) { + super(UUID.class); + _asBinary = asBinary; + } @Override public boolean isEmpty(SerializerProvider prov, UUID value) @@ -45,6 +62,7 @@ public JsonSerializer createContextual(SerializerProvider serializers, JsonFormat.Value format = findFormatOverrides(serializers, property, handledType()); Boolean asBinary = null; + if (format != null) { JsonFormat.Shape shape = format.getShape(); if (shape == JsonFormat.Shape.BINARY) { @@ -54,7 +72,9 @@ public JsonSerializer createContextual(SerializerProvider serializers, } // otherwise leave as `null` meaning about same as NATURAL } - // !!! TODO: + if (asBinary != _asBinary) { + return new UUIDSerializer(asBinary); + } return this; } @@ -63,15 +83,9 @@ public void serialize(UUID value, JsonGenerator gen, SerializerProvider provider throws IOException { // First: perhaps we could serialize it as raw binary data? - if (gen.canWriteBinaryNatively()) { - // 07-Dec-2013, tatu: One nasty case; that of TokenBuffer. While it can - // technically retain binary data, we do not want to do use binary - // with it, as that results in UUIDs getting converted to Base64 for - // most conversions. - if (!(gen instanceof TokenBuffer)) { - gen.writeBinary(_asBytes(value)); - return; - } + if (_writeAsBinary(gen)) { + gen.writeBinary(_asBytes(value)); + return; } // UUID.toString() works ok functionally, but we can make it go much faster @@ -96,6 +110,20 @@ public void serialize(UUID value, JsonGenerator gen, SerializerProvider provider gen.writeString(ch, 0, 36); } + // @since 2.11.3 + protected boolean _writeAsBinary(JsonGenerator g) + { + if (_asBinary != null) { + return _asBinary; + } + // 07-Dec-2013, tatu: One nasty case; that of TokenBuffer. While it can + // technically retain binary data, we do not want to do use binary + // with it, as that results in UUIDs getting converted to Base64 for + // most conversions. + return !(g instanceof TokenBuffer) && g.canWriteBinaryNatively(); + } + + // Need to add bit of extra info, format @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java index 289e2696ed..5ba7dd4bdf 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/JDKTypeSerializationTest.java @@ -18,9 +18,9 @@ * that are not Enums, Collections, Maps, or standard Date/Time types) */ public class JDKTypeSerializationTest - extends com.fasterxml.jackson.databind.BaseMapTest + extends BaseMapTest { - private final ObjectMapper MAPPER = objectMapper(); + private final ObjectMapper MAPPER = sharedMapper(); static class InetAddressBean { public InetAddress value; @@ -182,40 +182,6 @@ public void testDuplicatedByteBufferWithCustomPosition() throws IOException assertEquals(exp, MAPPER.writeValueAsString(bbuf.duplicate())); } - // Verify that efficient UUID codec won't mess things up: - public void testUUIDs() throws IOException - { - // first, couple of generated UUIDs: - for (String value : new String[] { - "76e6d183-5f68-4afa-b94a-922c1fdb83f8", - "540a88d1-e2d8-4fb1-9396-9212280d0a7f", - "2c9e441d-1cd0-472d-9bab-69838f877574", - "591b2869-146e-41d7-8048-e8131f1fdec5", - "82994ac2-7b23-49f2-8cc5-e24cf6ed77be", - "00000007-0000-0000-0000-000000000000" - }) { - UUID uuid = UUID.fromString(value); - String json = MAPPER.writeValueAsString(uuid); - assertEquals(quote(uuid.toString()), json); - - // Also, wrt [#362], should convert cleanly - String str = MAPPER.convertValue(uuid, String.class); - assertEquals(value, str); - } - - // then use templating; note that these are not exactly valid UUIDs - // wrt spec (type bits etc), but JDK UUID should deal ok - final String TEMPL = "00000000-0000-0000-0000-000000000000"; - final String chars = "123456789abcdef"; - - for (int i = 0; i < chars.length(); ++i) { - String value = TEMPL.replace('0', chars.charAt(i)); - UUID uuid = UUID.fromString(value); - String json = MAPPER.writeValueAsString(uuid); - assertEquals(quote(uuid.toString()), json); - } - } - // [databind#2197] public void testVoidSerialization() throws Exception { diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/jdk/UUIDSerializationTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/UUIDSerializationTest.java new file mode 100644 index 0000000000..c0ed555e3b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/jdk/UUIDSerializationTest.java @@ -0,0 +1,82 @@ +package com.fasterxml.jackson.databind.ser.jdk; + +import java.io.IOException; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import com.fasterxml.jackson.databind.*; + +public class UUIDSerializationTest extends BaseMapTest +{ + static class UUIDWrapperVanilla { + public UUID uuid; + + public UUIDWrapperVanilla(UUID u) { uuid = u; } + } + + static class UUIDWrapperBinary { + // default with JSON is String, for use of (base64-encoded) Binary: + @JsonFormat(shape = JsonFormat.Shape.BINARY) + public UUID uuid; + + public UUIDWrapperBinary(UUID u) { uuid = u; } + } + + private final ObjectMapper MAPPER = sharedMapper(); + + // Verify that efficient UUID codec won't mess things up: + public void testBasicUUIDs() throws IOException + { + // first, couple of generated UUIDs: + for (String value : new String[] { + "76e6d183-5f68-4afa-b94a-922c1fdb83f8", + "540a88d1-e2d8-4fb1-9396-9212280d0a7f", + "2c9e441d-1cd0-472d-9bab-69838f877574", + "591b2869-146e-41d7-8048-e8131f1fdec5", + "82994ac2-7b23-49f2-8cc5-e24cf6ed77be", + "00000007-0000-0000-0000-000000000000" + }) { + UUID uuid = UUID.fromString(value); + String json = MAPPER.writeValueAsString(uuid); + assertEquals(quote(uuid.toString()), json); + + // Also, wrt [#362], should convert cleanly + String str = MAPPER.convertValue(uuid, String.class); + assertEquals(value, str); + } + + // then use templating; note that these are not exactly valid UUIDs + // wrt spec (type bits etc), but JDK UUID should deal ok + final String TEMPL = "00000000-0000-0000-0000-000000000000"; + final String chars = "123456789abcdef"; + + for (int i = 0; i < chars.length(); ++i) { + String value = TEMPL.replace('0', chars.charAt(i)); + UUID uuid = UUID.fromString(value); + String json = MAPPER.writeValueAsString(uuid); + assertEquals(quote(uuid.toString()), json); + } + } + + public void testShapeOverrides() throws Exception + { + final String nullUUIDStr = "00000000-0000-0000-0000-000000000000"; + final UUID nullUUID = UUID.fromString(nullUUIDStr); + + // First, see that Binary per-property override works: + assertEquals("{\"uuid\":\"AAAAAAAAAAAAAAAAAAAAAA==\"}", + MAPPER.writeValueAsString(new UUIDWrapperBinary(nullUUID))); + + // but that without one we'd get String + assertEquals("{\"uuid\":\""+nullUUIDStr+"\"}", + MAPPER.writeValueAsString(new UUIDWrapperVanilla(nullUUID))); + + // but can also override by type + ObjectMapper m = newJsonMapper(); + m.configOverride(UUID.class) + .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.BINARY)); + assertEquals("{\"uuid\":\"AAAAAAAAAAAAAAAAAAAAAA==\"}", + m.writeValueAsString(new UUIDWrapperVanilla(nullUUID))); + } +}