From 810f2a438414b68127253e282a10a95b370214ab Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 14 Oct 2026 12:18:26 -0700 Subject: [PATCH 1/4] Re-add `@JsonWrapped` annotation (redo of #346) Reverts #362, which reverted #346. --- release-notes/VERSION-2.x | 3 +- .../jackson/annotation/JsonWrapped.java | 82 +++++++++++++++++++ .../jackson/annotation/JsonWrappedTest.java | 53 ++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java create mode 100644 src/test/java/com/fasterxml/jackson/annotation/JsonWrappedTest.java diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index a74ba874..8ab54ec8 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -16,7 +16,8 @@ NOTE: Jackson 3.x components rely on 2.x annotations; there are no separate 2.23 (not yet released) -No changes since 2.22 +#346: Add `@JsonWrapped` annotation + (contributed by @sri-adarsh-kumar) 2.22 (31-May-2026) diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java b/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java new file mode 100644 index 00000000..e4d2cfa6 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java @@ -0,0 +1,82 @@ +package com.fasterxml.jackson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that groups one or more bean properties into a synthetic + * nested JSON object during serialization, and extracts them back during + * deserialization. This is the inverse of {@link JsonUnwrapped}. + * + *

Multiple fields annotated with the same {@code value()} are grouped into + * a single wrapper object. Inner property names follow Jackson's standard naming + * ({@code @JsonProperty} or default). + * + *

Example: given a POJO such as: + *

+ * public class Gene {
+ *     public String name;
+ *
+ *     @JsonWrapped("chr")
+ *     public String chromosome;
+ *
+ *     @JsonWrapped("chr")
+ *     public int position;
+ * }
+ * 
+ * serialization produces: + *
+ * {
+ *   "name" : "BRCA1",
+ *   "chr" : {
+ *     "chromosome" : "17",
+ *     "position" : 43044295
+ *   }
+ * }
+ * 
+ * + *

Constraints: + *

+ * + * @see JsonUnwrapped + * @since 2.23 + */ +@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotation +public @interface JsonWrapped { + /** + * Single-level wrapper object name (e.g. "chr"). + * An empty string disables wrapping (useful in mix-ins to suppress + * wrapping defined in a supertype). + */ + String value(); + + /** + * Property that is usually only used when overriding (masking) annotations, + * using mix-in annotations. Otherwise default value of {@code true} is fine, + * and value need not be explicitly included. + */ + boolean enabled() default true; +} diff --git a/src/test/java/com/fasterxml/jackson/annotation/JsonWrappedTest.java b/src/test/java/com/fasterxml/jackson/annotation/JsonWrappedTest.java new file mode 100644 index 00000000..8d243d3d --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/annotation/JsonWrappedTest.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.annotation; + +import java.lang.reflect.Constructor; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class JsonWrappedTest + extends AnnotationTestUtil +{ + private static class BeanWithField { + @JsonWrapped("wrapper") + public String field; + } + + private static class BeanWithMethod { + @JsonWrapped("wrapper") + public String getField() { return null; } + } + + private static class BeanWithParam { + public BeanWithParam(@JsonWrapped("wrapper") String field) { } + } + + @JsonWrapped("wrapper") + @JacksonAnnotationsInside + @interface BundleAnnotation { } + + @Test + public void testRuntimeRetentionOnField() throws Exception { + JsonWrapped ann = BeanWithField.class.getField("field").getAnnotation(JsonWrapped.class); + assertNotNull(ann); + assertTrue(ann.enabled()); + } + + @Test + public void testRuntimeRetentionOnMethod() throws Exception { + JsonWrapped ann = BeanWithMethod.class.getMethod("getField").getAnnotation(JsonWrapped.class); + assertNotNull(ann); + } + + @Test + public void testApplicableOnConstructorParameter() throws Exception { + Constructor ctor = BeanWithParam.class.getDeclaredConstructor(String.class); + JsonWrapped ann = ctor.getParameters()[0].getAnnotation(JsonWrapped.class); + assertNotNull(ann); + } + + @Test + public void testApplicableOnAnnotationType() { + JsonWrapped ann = BundleAnnotation.class.getAnnotation(JsonWrapped.class); + assertNotNull(ann); + } +} From 69d386b05bce51e1797a4d548a27bf93af700449 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 14 Oct 2026 12:20:49 -0700 Subject: [PATCH 2/4] Update release notes --- release-notes/VERSION-2.x | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 8ab54ec8..8616496f 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -14,11 +14,15 @@ NOTE: Jackson 3.x components rely on 2.x annotations; there are no separate === Releases === ------------------------------------------------------------------------ -2.23 (not yet released) +2.24 (not yet released) #346: Add `@JsonWrapped` annotation (contributed by @sri-adarsh-kumar) +2.23 (not yet released) + +No changes since 2.22 + 2.22 (31-May-2026) #78: Add `@JsonApplyView` to allow changing active JsonView on submodels From beec5c639fd40b901573479e729c5fe3241519a6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 14 Oct 2026 12:23:35 -0700 Subject: [PATCH 3/4] update release notes --- release-notes/VERSION-2.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 8616496f..400473f3 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -16,7 +16,7 @@ NOTE: Jackson 3.x components rely on 2.x annotations; there are no separate 2.24 (not yet released) -#346: Add `@JsonWrapped` annotation +#42: Add `@JsonWrapped` (to complement `@JsonUnwrapped`) (contributed by @sri-adarsh-kumar) 2.23 (not yet released) From f110d04ba7fec50f9a7a05ac96e6ed3127ae8bd3 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 14 Oct 2026 12:24:15 -0700 Subject: [PATCH 4/4] Fix @since to 2.24 --- src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java b/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java index e4d2cfa6..af1c8610 100644 --- a/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java +++ b/src/main/java/com/fasterxml/jackson/annotation/JsonWrapped.java @@ -60,7 +60,7 @@ * * * @see JsonUnwrapped - * @since 2.23 + * @since 2.24 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME)