diff --git a/adapter/src/main/java/com/alibaba/fastjson2/adapter/jackson/annotation/JsonFormat.java b/adapter/src/main/java/com/alibaba/fastjson2/adapter/jackson/annotation/JsonFormat.java new file mode 100644 index 0000000000..87d03fd172 --- /dev/null +++ b/adapter/src/main/java/com/alibaba/fastjson2/adapter/jackson/annotation/JsonFormat.java @@ -0,0 +1,13 @@ +package com.alibaba.fastjson2.adapter.jackson.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, + ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface JsonFormat { + String pattern() default ""; +} diff --git a/adapter/src/test/java/com/alibaba/fastjson2/adapter/jackson/databind/JsonFormatTest.java b/adapter/src/test/java/com/alibaba/fastjson2/adapter/jackson/databind/JsonFormatTest.java new file mode 100644 index 0000000000..3d7233425a --- /dev/null +++ b/adapter/src/test/java/com/alibaba/fastjson2/adapter/jackson/databind/JsonFormatTest.java @@ -0,0 +1,100 @@ +package com.alibaba.fastjson2.adapter.jackson.databind; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat; +import org.junit.jupiter.api.Test; + +import java.time.LocalTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JsonFormatTest { + @Test + public void test() { + Bean bean = new Bean(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean bean1 = JSON.parseObject(str, Bean.class); + assertEquals(bean.time, bean1.time); + } + + public static class Bean { + @JsonFormat(pattern = "HHmmss") + public LocalTime time; + } + + @Test + public void test1() { + Bean1 bean = new Bean1(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean1 bean1 = JSON.parseObject(str, Bean1.class); + assertEquals(bean.time, bean1.time); + } + + public static class Bean1 { + private LocalTime time; + + @JsonFormat(pattern = "HHmmss") + public LocalTime getTime() { + return time; + } + + @JsonFormat(pattern = "HHmmss") + public void setTime(LocalTime time) { + this.time = time; + } + } + + @Test + public void test3() { + Bean3 bean = new Bean3(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean3 bean1 = JSON.parseObject(str, Bean3.class); + assertEquals(bean.time, bean1.time); + } + + @Test + public void test3F() { + Bean3 bean = new Bean3(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean, JSONWriter.Feature.FieldBased); + assertEquals("{\"time\":\"121314\"}", str); + Bean3 bean1 = JSON.parseObject(str, Bean3.class, JSONReader.Feature.FieldBased); + assertEquals(bean.time, bean1.time); + } + + @JsonFormat(pattern = "HHmmss") + public static class Bean3 { + public LocalTime time; + } + + @Test + public void test4() { + Bean4 bean = new Bean4(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean4 bean1 = JSON.parseObject(str, Bean4.class); + assertEquals(bean.time, bean1.time); + } + + @JsonFormat(pattern = "HHmmss") + public static class Bean4 { + private LocalTime time; + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } + } +} diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java index 4f087a4710..23f52ca3d6 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java @@ -26,6 +26,7 @@ import java.util.regex.Pattern; import static com.alibaba.fastjson2.util.AnnotationUtils.getAnnotations; +import static com.alibaba.fastjson2.util.BeanUtils.processJacksonJsonFormat; import static com.alibaba.fastjson2.util.BeanUtils.processJacksonJsonIgnore; import static com.alibaba.fastjson2.util.JDKUtils.UNSAFE_SUPPORT; @@ -255,6 +256,12 @@ public void getBeanInfo(BeanInfo beanInfo, Class objectClass) { processJacksonJsonTypeName(beanInfo, annotation); } break; + case "com.fasterxml.jackson.annotation.JsonFormat": + case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat": + if (useJacksonAnnotation) { + processJacksonJsonFormat(beanInfo, annotation); + } + break; case "com.fasterxml.jackson.annotation.JsonSubTypes": case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonSubTypes": if (useJacksonAnnotation) { @@ -707,6 +714,12 @@ public void getFieldInfo(FieldInfo fieldInfo, Class objectClass, Method method) processJacksonJsonDeserialize(fieldInfo, annotation); } break; + case "com.fasterxml.jackson.annotation.JsonFormat": + case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat": + if (useJacksonAnnotation) { + processJacksonJsonFormat(fieldInfo, annotation); + } + break; case "com.fasterxml.jackson.annotation.JsonAnySetter": case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonAnySetter": if (useJacksonAnnotation) { @@ -822,6 +835,12 @@ private void processAnnotation(FieldInfo fieldInfo, Annotation[] annotations) { processJacksonJsonProperty(fieldInfo, annotation); } break; + case "com.fasterxml.jackson.annotation.JsonFormat": + case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat": + if (useJacksonAnnotation) { + processJacksonJsonFormat(fieldInfo, annotation); + } + break; case "com.fasterxml.jackson.databind.annotation.JsonDeserialize": case "com.alibaba.fastjson2.adapter.jackson.databind.annotation.JsonDeserialize": if (useJacksonAnnotation) { diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java index 1b82063d0b..74d03e8b97 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java @@ -1297,6 +1297,7 @@ protected FieldReader[] createFieldReaders( BeanInfo finalBeanInfo = beanInfo; final long beanFeatures = beanInfo.readerFeatures; + final String beanFormat = beanInfo.format; final FieldInfo fieldInfo = new FieldInfo(); final String[] orders = beanInfo.orders; if (fieldBased) { @@ -1304,6 +1305,8 @@ protected FieldReader[] createFieldReaders( fieldInfo.init(); fieldInfo.features |= JSONReader.Feature.FieldBased.mask; fieldInfo.features |= beanFeatures; + fieldInfo.format = beanFormat; + createFieldReader(objectClass, objectType, namingStrategy, fieldInfo, field, fieldReaders, provider); }); } else { @@ -1311,6 +1314,7 @@ protected FieldReader[] createFieldReaders( fieldInfo.init(); fieldInfo.ignore = (field.getModifiers() & Modifier.PUBLIC) == 0; fieldInfo.features |= beanFeatures; + fieldInfo.format = beanFormat; createFieldReader(objectClass, objectType, namingStrategy, fieldInfo, field, fieldReaders, provider); if (fieldInfo.required) { @@ -1325,6 +1329,7 @@ protected FieldReader[] createFieldReaders( BeanUtils.setters(objectClass, method -> { fieldInfo.init(); fieldInfo.features |= beanFeatures; + fieldInfo.format = beanFormat; createFieldReader(objectClass, objectType, namingStrategy, orders, fieldInfo, method, fieldReaders, provider); }); diff --git a/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java b/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java index 1e43b1eca6..ea0b1ab34f 100644 --- a/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java +++ b/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java @@ -2278,4 +2278,50 @@ public static void processJSONType1x(BeanInfo beanInfo, Annotation jsonType1x, M ignored.printStackTrace(); } } + + public static void processJacksonJsonFormat(FieldInfo fieldInfo, Annotation annotation) { + Class annotationClass = annotation.getClass(); + BeanUtils.annotationMethods(annotationClass, m -> { + String name = m.getName(); + try { + Object result = m.invoke(annotation); + switch (name) { + case "pattern": { + String pattern = (String) result; + if (pattern.length() != 0) { + fieldInfo.format = pattern; + } + break; + } + default: + break; + } + } catch (Throwable ignored) { + // ignored + } + }); + } + + public static void processJacksonJsonFormat(BeanInfo beanInfo, Annotation annotation) { + Class annotationClass = annotation.getClass(); + BeanUtils.annotationMethods(annotationClass, m -> { + String name = m.getName(); + try { + Object result = m.invoke(annotation); + switch (name) { + case "pattern": { + String pattern = (String) result; + if (pattern.length() != 0) { + beanInfo.format = pattern; + } + break; + } + default: + break; + } + } catch (Throwable ignored) { + // ignored + } + }); + } } diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java index 32a857fb4b..055f6b471b 100644 --- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java +++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java @@ -27,6 +27,7 @@ import static com.alibaba.fastjson2.codec.FieldInfo.JSON_AUTO_WIRED_ANNOTATED; import static com.alibaba.fastjson2.util.AnnotationUtils.getAnnotations; +import static com.alibaba.fastjson2.util.BeanUtils.processJacksonJsonFormat; import static com.alibaba.fastjson2.util.BeanUtils.processJacksonJsonIgnore; public class ObjectWriterBaseModule @@ -111,6 +112,12 @@ public void getBeanInfo(BeanInfo beanInfo, Class objectClass) { processJacksonJsonPropertyOrder(beanInfo, annotation); } break; + case "com.fasterxml.jackson.annotation.JsonFormat": + case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat": + if (useJacksonAnnotation) { + processJacksonJsonFormat(beanInfo, annotation); + } + break; case "com.fasterxml.jackson.annotation.JsonTypeInfo": case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonTypeInfo": if (useJacksonAnnotation) { @@ -343,6 +350,12 @@ public void getFieldInfo(BeanInfo beanInfo, FieldInfo fieldInfo, Class objectTyp processJacksonJsonProperty(fieldInfo, annotation); } break; + case "com.fasterxml.jackson.annotation.JsonFormat": + case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat": + if (useJacksonAnnotation) { + processJacksonJsonFormat(fieldInfo, annotation); + } + break; case "com.alibaba.fastjson2.adapter.jackson.databind.annotation.JsonSerialize": case "com.fasterxml.jackson.databind.annotation.JsonSerialize": if (useJacksonAnnotation) { @@ -870,6 +883,12 @@ private void processAnnotations(FieldInfo fieldInfo, Annotation[] annotations) { } break; } + case "com.fasterxml.jackson.annotation.JsonFormat": + case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonFormat": + if (useJacksonAnnotation) { + processJacksonJsonFormat(fieldInfo, annotation); + } + break; case "com.fasterxml.jackson.annotation.JsonValue": case "com.alibaba.fastjson2.adapter.jackson.annotation.JsonValue": if (useJacksonAnnotation) { diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java index 3e0d7efcca..df4909d2a3 100644 --- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java +++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java @@ -218,7 +218,12 @@ protected FieldWriter creteFieldWriter( } } - return createFieldWriter(provider, fieldName, fieldInfo.ordinal, fieldInfo.features, fieldInfo.format, fieldInfo.label, field, writeUsingWriter); + String format = fieldInfo.format; + if (format == null && beanInfo.format != null) { + format = beanInfo.format; + } + + return createFieldWriter(provider, fieldName, fieldInfo.ordinal, fieldInfo.features, format, fieldInfo.label, field, writeUsingWriter); } protected ObjectWriter getAnnotatedObjectWriter(ObjectWriterProvider provider, diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java index a82f1b2d59..763c25ec0e 100644 --- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java +++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java @@ -249,6 +249,8 @@ boolean record = BeanUtils.isRecord(objectClass); BeanUtils.getters(objectClass, method -> { fieldInfo.init(); fieldInfo.features |= writerFieldFeatures; + fieldInfo.format = beanInfo.format; + provider.getFieldInfo(beanInfo, fieldInfo, objectClass, method); if (fieldInfo.ignore) { return; diff --git a/core/src/test/java/com/alibaba/fastjson2/jackson_support/JsonFormatTest.java b/core/src/test/java/com/alibaba/fastjson2/jackson_support/JsonFormatTest.java new file mode 100644 index 0000000000..48b0dfdcb6 --- /dev/null +++ b/core/src/test/java/com/alibaba/fastjson2/jackson_support/JsonFormatTest.java @@ -0,0 +1,100 @@ +package com.alibaba.fastjson2.jackson_support; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.fasterxml.jackson.annotation.JsonFormat; +import org.junit.jupiter.api.Test; + +import java.time.LocalTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JsonFormatTest { + @Test + public void test() { + Bean bean = new Bean(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean bean1 = JSON.parseObject(str, Bean.class); + assertEquals(bean.time, bean1.time); + } + + public static class Bean { + @JsonFormat(pattern = "HHmmss") + public LocalTime time; + } + + @Test + public void test1() { + Bean1 bean = new Bean1(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean1 bean1 = JSON.parseObject(str, Bean1.class); + assertEquals(bean.time, bean1.time); + } + + public static class Bean1 { + private LocalTime time; + + @JsonFormat(pattern = "HHmmss") + public LocalTime getTime() { + return time; + } + + @JsonFormat(pattern = "HHmmss") + public void setTime(LocalTime time) { + this.time = time; + } + } + + @Test + public void test3() { + Bean3 bean = new Bean3(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean3 bean1 = JSON.parseObject(str, Bean3.class); + assertEquals(bean.time, bean1.time); + } + + @Test + public void test3F() { + Bean3 bean = new Bean3(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean, JSONWriter.Feature.FieldBased); + assertEquals("{\"time\":\"121314\"}", str); + Bean3 bean1 = JSON.parseObject(str, Bean3.class, JSONReader.Feature.FieldBased); + assertEquals(bean.time, bean1.time); + } + + @JsonFormat(pattern = "HHmmss") + public static class Bean3 { + public LocalTime time; + } + + @Test + public void test4() { + Bean4 bean = new Bean4(); + bean.time = LocalTime.of(12, 13, 14); + String str = JSON.toJSONString(bean); + assertEquals("{\"time\":\"121314\"}", str); + Bean4 bean1 = JSON.parseObject(str, Bean4.class); + assertEquals(bean.time, bean1.time); + } + + @JsonFormat(pattern = "HHmmss") + public static class Bean4 { + private LocalTime time; + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } + } +}