From 2b474b388220ccf858bc7d42db7de63c09ac8660 Mon Sep 17 00:00:00 2001 From: mariuszs Date: Mon, 9 Mar 2026 21:13:55 +0100 Subject: [PATCH 1/3] feat: add configurable default serialization format to Jackson modules Allow choosing between Base62 (URL62) and standard UUID (RAW) format at module level via new FriendlyIdModule(FriendlyIdFormat) constructor. Default behavior (Base62) is preserved for backward compatibility. --- .../jackson/FriendlyIdDeserializer.java | 2 +- .../friendly_id/jackson/FriendlyIdModule.java | 27 +++++++++++++++++-- .../jackson/FriendlyIdSerializer.java | 2 +- .../jackson2/FriendlyIdJackson2Module.java | 16 ++++++++++- .../jackson2/FriendlyIdSerializer.java | 13 ++++++++- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdDeserializer.java b/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdDeserializer.java index 1d3cc58..541854e 100644 --- a/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdDeserializer.java +++ b/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdDeserializer.java @@ -22,7 +22,7 @@ public FriendlyIdDeserializer() { this(true); } - private FriendlyIdDeserializer(boolean useFriendlyFormat) { + FriendlyIdDeserializer(boolean useFriendlyFormat) { super(UUID.class); this.useFriendlyFormat = useFriendlyFormat; } diff --git a/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdModule.java b/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdModule.java index 1401403..69fe9e8 100644 --- a/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdModule.java +++ b/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdModule.java @@ -4,6 +4,7 @@ import tools.jackson.databind.module.SimpleModule; +import com.devskiller.friendly_id.FriendlyIdFormat; import com.devskiller.friendly_id.type.FriendlyId; /** @@ -12,14 +13,36 @@ * This module registers custom serializers and deserializers for UUID and FriendlyId types, * enabling automatic conversion between UUID values and their FriendlyId string representation. *

+ *

+ * By default, UUIDs are serialized as Base62 FriendlyIds. Use {@link FriendlyIdFormat#RAW} + * to serialize UUIDs in standard format while still accepting both formats on deserialization. + * Per-field format can always be overridden with {@link com.devskiller.friendly_id.IdFormat @IdFormat}. + *

*/ public class FriendlyIdModule extends SimpleModule { + /** + * Creates a module with default FriendlyId (Base62) serialization format. + */ public FriendlyIdModule() { + this(FriendlyIdFormat.URL62); + } + + /** + * Creates a module with the specified default serialization format. + *

+ * When {@link FriendlyIdFormat#RAW} is used, UUIDs are serialized in standard format + * but deserialization still accepts both standard UUIDs and Base62 FriendlyIds. + * Per-field format can be overridden with {@link com.devskiller.friendly_id.IdFormat @IdFormat}. + * + * @param defaultFormat the default serialization format for UUID fields + */ + public FriendlyIdModule(FriendlyIdFormat defaultFormat) { super("FriendlyIdModule"); + boolean useFriendlyFormat = defaultFormat == FriendlyIdFormat.URL62; - // UUID serializers/deserializers - addSerializer(UUID.class, new FriendlyIdSerializer()); + // UUID serializers/deserializers — deserializer always accepts both formats + addSerializer(UUID.class, new FriendlyIdSerializer(useFriendlyFormat)); addDeserializer(UUID.class, new FriendlyIdDeserializer()); // FriendlyId value object serializers/deserializers diff --git a/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdSerializer.java b/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdSerializer.java index df83a20..308d98f 100644 --- a/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdSerializer.java +++ b/friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdSerializer.java @@ -20,7 +20,7 @@ public FriendlyIdSerializer() { this(true); } - private FriendlyIdSerializer(boolean useFriendlyFormat) { + FriendlyIdSerializer(boolean useFriendlyFormat) { super(UUID.class); this.useFriendlyFormat = useFriendlyFormat; } diff --git a/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdJackson2Module.java b/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdJackson2Module.java index 3e9fc3b..3ceb5c8 100644 --- a/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdJackson2Module.java +++ b/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdJackson2Module.java @@ -2,6 +2,7 @@ import java.util.UUID; +import com.devskiller.friendly_id.FriendlyIdFormat; import com.devskiller.friendly_id.type.FriendlyId; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -9,10 +10,23 @@ public class FriendlyIdJackson2Module extends SimpleModule { private final FriendlyIdAnnotationIntrospector introspector; + /** + * Creates a module with default FriendlyId (Base62) serialization format. + */ public FriendlyIdJackson2Module() { + this(FriendlyIdFormat.URL62); + } + + /** + * Creates a module with the specified default serialization format. + * + * @param defaultFormat the default serialization format for UUID fields + */ + public FriendlyIdJackson2Module(FriendlyIdFormat defaultFormat) { + boolean useFriendlyFormat = defaultFormat == FriendlyIdFormat.URL62; introspector = new FriendlyIdAnnotationIntrospector(); addDeserializer(UUID.class, new FriendlyIdDeserializer()); - addSerializer(UUID.class, new FriendlyIdSerializer()); + addSerializer(UUID.class, new FriendlyIdSerializer(useFriendlyFormat)); // Add serializer/deserializer for FriendlyId value object addDeserializer(FriendlyId.class, new FriendlyIdValueDeserializer()); diff --git a/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdSerializer.java b/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdSerializer.java index 49bae14..472572e 100644 --- a/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdSerializer.java +++ b/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdSerializer.java @@ -11,12 +11,23 @@ public class FriendlyIdSerializer extends StdSerializer { + private final boolean useFriendlyFormat; + public FriendlyIdSerializer() { + this(true); + } + + FriendlyIdSerializer(boolean useFriendlyFormat) { super(UUID.class); + this.useFriendlyFormat = useFriendlyFormat; } @Override public void serialize(UUID uuid, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeString(FriendlyIds.toFriendlyId(uuid)); + if (useFriendlyFormat) { + jsonGenerator.writeString(FriendlyIds.toFriendlyId(uuid)); + } else { + jsonGenerator.writeString(uuid.toString()); + } } } From 477bdd208ed9530b4f374a510221503dd2fad392 Mon Sep 17 00:00:00 2001 From: mariuszs Date: Mon, 9 Mar 2026 21:17:02 +0100 Subject: [PATCH 2/3] test: add RawFormatModuleTest for configurable default format fix: simplify AnnotationIntrospector to only override when @IdFormat is present, allowing module-level default format to take effect --- .../spring/RawFormatModuleTest.java | 71 +++++++++++++++++++ .../FriendlyIdAnnotationIntrospector.java | 29 +++----- .../spring/RawFormatModuleTest.java | 57 +++++++++++++++ 3 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java create mode 100644 friendly-id-jackson2-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java diff --git a/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java b/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java new file mode 100644 index 0000000..47e6058 --- /dev/null +++ b/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java @@ -0,0 +1,71 @@ +package com.devskiller.friendly_id.spring; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import tools.jackson.databind.json.JsonMapper; + +import com.devskiller.friendly_id.FriendlyIdFormat; +import com.devskiller.friendly_id.FriendlyIds; +import com.devskiller.friendly_id.jackson.FriendlyIdModule; + +import static org.assertj.core.api.Assertions.assertThat; + +class RawFormatModuleTest { + + private final UUID uuid = UUID.fromString("f088ce5b-9279-4cc3-946a-c15ad740dd6d"); + + private final JsonMapper mapper = JsonMapper.builder() + .addModule(new FriendlyIdModule(FriendlyIdFormat.RAW)) + .build(); + + @Test + void shouldSerializeUuidInStandardFormat() { + String json = mapper.writeValueAsString(uuid); + + assertThat(json).isEqualTo("\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + } + + @Test + void shouldDeserializeStandardUuid() { + UUID result = mapper.readValue("\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\"", UUID.class); + + assertThat(result).isEqualTo(uuid); + } + + @Test + void shouldDeserializeFriendlyIdWhenModuleIsRaw() { + String friendlyId = FriendlyIds.toFriendlyId(uuid); + + UUID result = mapper.readValue("\"" + friendlyId + "\"", UUID.class); + + assertThat(result).isEqualTo(uuid); + } + + @Test + void shouldRespectPerFieldOverrideWhenModuleIsRaw() { + var foo = new Foo(uuid, uuid); + + String json = mapper.writeValueAsString(foo); + + // rawUuid has @IdFormat(RAW) -> standard format + assertThat(json).contains("\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + // friendlyId has no annotation, module default is RAW -> standard format + assertThat(json).contains("\"friendlyId\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + } + + @Test + void shouldSerializeFriendlyFormatWithUrl62Override() { + var mapper = JsonMapper.builder() + .addModule(new FriendlyIdModule(FriendlyIdFormat.RAW)) + .build(); + + // Foo: rawUuid=@IdFormat(RAW), friendlyId=no annotation (module default RAW) + // Both should be standard UUID when module is RAW + var foo = new Foo(uuid, uuid); + String json = mapper.writeValueAsString(foo); + + assertThat(json).contains("\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + assertThat(json).contains("\"friendlyId\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + } +} diff --git a/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdAnnotationIntrospector.java b/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdAnnotationIntrospector.java index 6cd8e05..2cbf916 100644 --- a/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdAnnotationIntrospector.java +++ b/friendly-id-jackson2-datatype/src/main/java/com/devskiller/friendly_id/jackson2/FriendlyIdAnnotationIntrospector.java @@ -19,30 +19,23 @@ public class FriendlyIdAnnotationIntrospector extends JacksonAnnotationIntrospec @Override public Object findSerializer(Annotated annotatedMethod) { IdFormat annotation = _findAnnotation(annotatedMethod, IdFormat.class); - if (annotatedMethod.getRawType() == UUID.class) { - if (annotation != null) { - return switch (annotation.value()) { - case RAW -> UUIDSerializer.class; - case URL62 -> FriendlyIdSerializer.class; - }; - } - return FriendlyIdSerializer.class; - } else { - return null; + if (annotatedMethod.getRawType() == UUID.class && annotation != null) { + return switch (annotation.value()) { + case RAW -> UUIDSerializer.class; + case URL62 -> FriendlyIdSerializer.class; + }; } + return null; } @Override public Object findDeserializer(Annotated annotatedMethod) { var annotation = _findAnnotation(annotatedMethod, IdFormat.class); - if (rawDeserializationType(annotatedMethod) == UUID.class) { - if (annotation != null) { - return switch (annotation.value()) { - case RAW -> UUIDDeserializer.class; - case URL62 -> FriendlyIdDeserializer.class; - }; - } - return FriendlyIdDeserializer.class; + if (rawDeserializationType(annotatedMethod) == UUID.class && annotation != null) { + return switch (annotation.value()) { + case RAW -> UUIDDeserializer.class; + case URL62 -> FriendlyIdDeserializer.class; + }; } return null; } diff --git a/friendly-id-jackson2-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java b/friendly-id-jackson2-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java new file mode 100644 index 0000000..d5e45b1 --- /dev/null +++ b/friendly-id-jackson2-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java @@ -0,0 +1,57 @@ +package com.devskiller.friendly_id.spring; + +import java.util.UUID; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import com.devskiller.friendly_id.FriendlyIdFormat; +import com.devskiller.friendly_id.FriendlyIds; +import com.devskiller.friendly_id.jackson2.FriendlyIdJackson2Module; + +import static org.assertj.core.api.Assertions.assertThat; + +class RawFormatModuleTest { + + private final UUID uuid = UUID.fromString("f088ce5b-9279-4cc3-946a-c15ad740dd6d"); + + private final ObjectMapper mapper = new ObjectMapper() + .registerModule(new FriendlyIdJackson2Module(FriendlyIdFormat.RAW)); + + @Test + void shouldSerializeUuidInStandardFormat() throws Exception { + String json = mapper.writeValueAsString(uuid); + + assertThat(json).isEqualTo("\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + } + + @Test + void shouldDeserializeStandardUuid() throws Exception { + UUID result = mapper.readValue("\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\"", UUID.class); + + assertThat(result).isEqualTo(uuid); + } + + @Test + void shouldDeserializeFriendlyIdWhenModuleIsRaw() throws Exception { + String friendlyId = FriendlyIds.toFriendlyId(uuid); + + UUID result = mapper.readValue("\"" + friendlyId + "\"", UUID.class); + + assertThat(result).isEqualTo(uuid); + } + + @Test + void shouldRespectPerFieldAnnotationWhenModuleIsRaw() throws Exception { + Foo foo = new Foo(); + foo.setRawUuid(uuid); + foo.setFriendlyId(uuid); + + String json = mapper.writeValueAsString(foo); + + // rawUuid has @IdFormat(RAW) -> standard format + assertThat(json).contains("\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + // friendlyId has no annotation, module default is RAW -> standard format + assertThat(json).contains("\"friendlyId\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); + } +} From 8b9b58accc1ccdb8a056f9754ac4a9c25bc4ebbf Mon Sep 17 00:00:00 2001 From: mariuszs Date: Mon, 9 Mar 2026 21:26:00 +0100 Subject: [PATCH 3/3] test: remove duplicate test shouldSerializeFriendlyFormatWithUrl62Override Identical mapper setup and assertions as shouldRespectPerFieldOverrideWhenModuleIsRaw --- .../friendly_id/spring/RawFormatModuleTest.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java b/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java index 47e6058..4756592 100644 --- a/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java +++ b/friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/RawFormatModuleTest.java @@ -54,18 +54,4 @@ void shouldRespectPerFieldOverrideWhenModuleIsRaw() { assertThat(json).contains("\"friendlyId\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); } - @Test - void shouldSerializeFriendlyFormatWithUrl62Override() { - var mapper = JsonMapper.builder() - .addModule(new FriendlyIdModule(FriendlyIdFormat.RAW)) - .build(); - - // Foo: rawUuid=@IdFormat(RAW), friendlyId=no annotation (module default RAW) - // Both should be standard UUID when module is RAW - var foo = new Foo(uuid, uuid); - String json = mapper.writeValueAsString(foo); - - assertThat(json).contains("\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); - assertThat(json).contains("\"friendlyId\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\""); - } }