diff --git a/example-solon-test/README.md b/example-solon-test/README.md
new file mode 100644
index 000000000..8ad8f8f65
--- /dev/null
+++ b/example-solon-test/README.md
@@ -0,0 +1,20 @@
+### 启动方式
+
+运行 SolonTestApp.main 方法(或者直接运行单测 DemoTest )
+
+### 测试
+
+测试 Render 是否正常工作
+
+* 访问:[http://localhost:8080/demo?username=world&password=1234](http://localhost:8080/demo?username=world&password=1234)
+
+测试 找不到的地址异常(技术上和上面一样,Solon 所有的 Web 输出都会走:Render 接口)
+
+* 访问:[http://localhost:8080/error](http://localhost:8080/error)
+
+### 说明
+
+此演示,会自动触发 fastjson2-extension-solon 的:
+
+* Fastjson2RenderFactory
+* Fastjson2ActionExecutor
diff --git a/example-solon-test/pom.xml b/example-solon-test/pom.xml
new file mode 100644
index 000000000..9eace9d1e
--- /dev/null
+++ b/example-solon-test/pom.xml
@@ -0,0 +1,104 @@
+
+
+ 4.0.0
+
+ com.alibaba.fastjson2
+ fastjson2-parent
+ 2.0.54-SNAPSHOT
+ ../pom.xml
+
+
+ com.com.alibaba.fastjson2
+ example-solon-test
+ example-solon-test
+ example-solon-test
+
+
+ 8
+ true
+ true
+
+
+
+
+
+ aliyunmaven
+ aliyun
+ https://maven.aliyun.com/repository/public
+
+
+
+ snapshots
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+
+
+
+
+ aliyunmaven
+ aliyun
+ https://maven.aliyun.com/repository/public
+
+
+
+
+
+
+ org.noear
+ solon-parent
+ pom
+ ${solon.version}
+ import
+
+
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2-extension-solon
+ ${project.version}
+
+
+
+ org.noear
+ solon-boot-jdkhttp
+
+
+
+ org.noear
+ solon-logging-simple
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.noear
+ solon-test
+ test
+
+
+
+
+
+
+ org.noear
+ solon-maven-plugin
+ ${solon.version}
+
+
+ package
+
+ repackage
+
+
+
+
+
+
+
diff --git a/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/SolonTestApp.java b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/SolonTestApp.java
new file mode 100644
index 000000000..8c504bec6
--- /dev/null
+++ b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/SolonTestApp.java
@@ -0,0 +1,11 @@
+package com.alibaba.fastjson2.example.solontest;
+
+import org.noear.solon.Solon;
+import org.noear.solon.annotation.SolonMain;
+
+@SolonMain
+public class SolonTestApp {
+ public static void main(String[] args) {
+ Solon.start(SolonTestApp.class, args);
+ }
+}
diff --git a/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/config/JsonConfigurer.java b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/config/JsonConfigurer.java
new file mode 100644
index 000000000..2b475a82e
--- /dev/null
+++ b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/config/JsonConfigurer.java
@@ -0,0 +1,24 @@
+package com.alibaba.fastjson2.example.solontest.config;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2ActionExecutor;
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import org.noear.solon.annotation.Bean;
+import org.noear.solon.annotation.Configuration;
+
+/**
+ * @author noear
+ * @since 2024-10-01
+ */
+@Configuration
+public class JsonConfigurer {
+ @Bean
+ public void fastjson2(Fastjson2ActionExecutor executor, Fastjson2RenderFactory render) {
+// executor.config().config(
+// JSONReader.Feature.FieldBased,
+// JSONReader.Feature.SupportArrayToBean);
+//
+// render.addFeatures(
+// JSONWriter.Feature.WriteMapNullValue,
+// JSONWriter.Feature.PrettyFormat);
+ }
+}
diff --git a/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/controller/DemoController.java b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/controller/DemoController.java
new file mode 100644
index 000000000..3abad45ac
--- /dev/null
+++ b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/controller/DemoController.java
@@ -0,0 +1,17 @@
+package com.alibaba.fastjson2.example.solontest.controller;
+
+import com.alibaba.fastjson2.example.solontest.entity.User;
+import org.noear.solon.annotation.Controller;
+import org.noear.solon.annotation.Mapping;
+
+/**
+ * @author noear
+ * @since 2024-10-01
+ */
+@Controller
+public class DemoController {
+ @Mapping("/demo")
+ public User demo(User user) {
+ return user;
+ }
+}
diff --git a/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/controller/DemoErrorFilter.java b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/controller/DemoErrorFilter.java
new file mode 100644
index 000000000..924253782
--- /dev/null
+++ b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/controller/DemoErrorFilter.java
@@ -0,0 +1,27 @@
+package com.alibaba.fastjson2.example.solontest.controller;
+
+import org.noear.solon.annotation.Component;
+import org.noear.solon.core.exception.StatusException;
+import org.noear.solon.core.handle.Context;
+import org.noear.solon.core.handle.Filter;
+import org.noear.solon.core.handle.FilterChain;
+import org.noear.solon.core.handle.Result;
+
+/**
+ * @author noear
+ * @since 2024-10-01
+ */
+@Component
+public class DemoErrorFilter
+ implements Filter {
+ @Override
+ public void doFilter(Context ctx, FilterChain chain) throws Throwable {
+ try {
+ chain.doFilter(ctx);
+ } catch (StatusException e) {
+ ctx.render(Result.failure(e.getCode(), e.getMessage()));
+ } catch (Throwable e) {
+ ctx.render(Result.failure(500));
+ }
+ }
+}
diff --git a/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/entity/User.java b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/entity/User.java
new file mode 100644
index 000000000..f32f80b4a
--- /dev/null
+++ b/example-solon-test/src/main/java/com/alibaba/fastjson2/example/solontest/entity/User.java
@@ -0,0 +1,17 @@
+package com.alibaba.fastjson2.example.solontest.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author noear
+ * @since 2024-10-01
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class User {
+ private String username;
+ private String password;
+}
diff --git a/example-solon-test/src/main/resources/app.properties b/example-solon-test/src/main/resources/app.properties
new file mode 100644
index 000000000..0a0a9762f
--- /dev/null
+++ b/example-solon-test/src/main/resources/app.properties
@@ -0,0 +1,3 @@
+server.port=8080
+
+solon.logging.logger.root.level=INFO
diff --git a/example-solon-test/src/test/java/com/alibaba/fastjson2/example/solontest/DemoTest.java b/example-solon-test/src/test/java/com/alibaba/fastjson2/example/solontest/DemoTest.java
new file mode 100644
index 000000000..f5117bb3d
--- /dev/null
+++ b/example-solon-test/src/test/java/com/alibaba/fastjson2/example/solontest/DemoTest.java
@@ -0,0 +1,39 @@
+package com.alibaba.fastjson2.example.solontest;
+
+import org.junit.jupiter.api.Test;
+import org.noear.solon.test.HttpTester;
+import org.noear.solon.test.SolonTest;
+
+/**
+ * @author noear 2024/10/2 created
+ */
+@SolonTest(SolonTestApp.class)
+public class DemoTest
+ extends HttpTester {
+ @Test
+ public void ok_post_json() throws Exception {
+ String json = "{\"password\":\"1234\",\"username\":\"world\"}";
+
+ String json2 = path("/demo").bodyJson(json).post();
+
+ assert json.equals(json2);
+ }
+
+ @Test
+ public void ok_get() throws Exception {
+ String json = "{\"password\":\"1234\",\"username\":\"world\"}";
+
+ String json2 = path("/demo?username=world&password=1234").get();
+
+ assert json.equals(json2);
+ }
+
+ @Test
+ public void error() throws Exception {
+ String json = "{\"code\":404,\"description\":\"Not Found: GET /error\"}";
+
+ String json2 = path("/error").get();
+
+ assert json.equals(json2);
+ }
+}
diff --git a/extension-solon/pom.xml b/extension-solon/pom.xml
new file mode 100644
index 000000000..bf592af34
--- /dev/null
+++ b/extension-solon/pom.xml
@@ -0,0 +1,100 @@
+
+
+ 4.0.0
+
+ com.alibaba.fastjson2
+ fastjson2-parent
+ 2.0.54-SNAPSHOT
+ ../pom.xml
+
+
+ fastjson2-extension-solon
+ fastjson2-extension-solon
+ Fastjson is a JSON processor (JSON parser + JSON generator) written in Java
+ jar
+ https://github.com/alibaba/fastjson2
+ 2024
+
+
+
+ Apache 2
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+ A business-friendly OSS license
+
+
+
+ https://github.com/alibaba/fastjson2
+ scm:git:https://git@github.com/alibaba/fastjson2.git
+
+
+ Alibaba Group
+ https://github.com/alibaba
+
+
+
+ wenshao
+ wenshao
+ shaojin.wensj(at)alibaba-inc.com
+
+ Developer
+ Tech Leader
+
+ +8
+ https://github.com/wenshao
+
+
+ noear
+ noear
+ noear@live.cn
+ +8
+ https://github.com/noear
+
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ ${project.version}
+
+
+
+ org.noear
+ solon-serialization
+ ${solon.version}
+
+
+
+ org.noear
+ solon-logging-simple
+ ${solon.version}
+ test
+
+
+
+ org.noear
+ solon-test
+ ${solon.version}
+ test
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ com/alibaba/fastjson2/**/*.java
+
+
+ Asia/Shanghai
+
+
+
+
+
+
diff --git a/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2ActionExecutor.java b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2ActionExecutor.java
new file mode 100644
index 000000000..1ce8e7463
--- /dev/null
+++ b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2ActionExecutor.java
@@ -0,0 +1,149 @@
+package com.alibaba.fastjson2.support.solon;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.fastjson2.JSONReader;
+import org.noear.solon.core.handle.Context;
+import org.noear.solon.core.mvc.ActionExecuteHandlerDefault;
+import org.noear.solon.core.wrap.MethodWrap;
+import org.noear.solon.core.wrap.ParamWrap;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Json ActionExecuteHandler
+ *
+ * @author noear
+ * @author 夜の孤城
+ * @author 暮城留风
+ * @since 1.9
+ * @since 2024-10-01
+ * */
+public class Fastjson2ActionExecutor
+ extends ActionExecuteHandlerDefault {
+ private final Fastjson2StringSerializer serializer = new Fastjson2StringSerializer();
+
+ public Fastjson2ActionExecutor() {
+ serializer.getDeserializeConfig().config();
+ serializer.getDeserializeConfig().config(JSONReader.Feature.ErrorOnEnumNotMatch);
+ }
+
+ /**
+ * Gets the serialization interface
+ */
+ public Fastjson2StringSerializer getSerializer() {
+ return serializer;
+ }
+
+ /**
+ * Deserialize the configuration
+ */
+ public JSONReader.Context config() {
+ return getSerializer().getDeserializeConfig();
+ }
+
+ /**
+ * Match or not
+ *
+ * @param ctx Handling context
+ * @param mime Content type
+ */
+ @Override
+ public boolean matched(Context ctx, String mime) {
+ return serializer.matched(ctx, mime);
+ }
+
+ /**
+ * Converting body
+ *
+ * @param ctx Handling context
+ * @param mWrap Method wrappers
+ */
+ @Override
+ protected Object changeBody(Context ctx, MethodWrap mWrap) throws Exception {
+ return serializer.deserializeFromBody(ctx);
+ }
+
+ /**
+ * 转换 value
+ *
+ * @param ctx Handling context
+ * @param p Parameter wrappers
+ * @param pi Parameter index
+ * @param pt Parameter type
+ * @param bodyObj Body object
+ */
+ @Override
+ protected Object changeValue(Context ctx, ParamWrap p, int pi, Class> pt, Object bodyObj) throws Exception {
+ if (p.spec().isRequiredPath() || p.spec().isRequiredCookie() || p.spec().isRequiredHeader()) {
+ //If path、cookie, header?
+ return super.changeValue(ctx, p, pi, pt, bodyObj);
+ }
+
+ if (p.spec().isRequiredBody() == false && ctx.paramMap().containsKey(p.spec().getName())) {
+ //If path、queryString?
+ return super.changeValue(ctx, p, pi, pt, bodyObj);
+ }
+
+ if (bodyObj == null) {
+ return super.changeValue(ctx, p, pi, pt, bodyObj);
+ }
+
+ if (bodyObj instanceof JSONObject) {
+ JSONObject tmp = (JSONObject) bodyObj;
+
+ if (p.spec().isRequiredBody() == false) {
+ //
+ //If there is no body requirement; Try to find by attribute
+ //
+ if (tmp.containsKey(p.spec().getName())) {
+ //Supports conversions of generic types
+ if (p.spec().isGenericType()) {
+ return tmp.getObject(p.spec().getName(), p.getGenericType());
+ } else {
+ return tmp.getObject(p.spec().getName(), pt);
+ }
+ }
+ }
+
+ //Try the body conversion
+ if (pt.isPrimitive() || pt.getTypeName().startsWith("java.lang.")) {
+ return super.changeValue(ctx, p, pi, pt, bodyObj);
+ } else {
+ if (List.class.isAssignableFrom(pt)) {
+ return null;
+ }
+
+ if (pt.isArray()) {
+ return null;
+ }
+
+ //Generic transformations such as Map
+ if (p.spec().isGenericType()) {
+ return tmp.to(p.getGenericType());
+ } else {
+ return tmp.to(pt);
+ }
+ }
+ }
+
+ if (bodyObj instanceof JSONArray) {
+ JSONArray tmp = (JSONArray) bodyObj;
+ //If the argument is a non-collection type
+ if (!Collection.class.isAssignableFrom(pt)) {
+ return null;
+ }
+ //Collection Type Conversions
+ if (p.spec().isGenericType()) {
+ //Transforms a collection with generics
+ return tmp.to(p.getGenericType());
+ } else {
+ //Can be converted not only to a List but also to a Set
+ return tmp.to(pt);
+ }
+ }
+
+ return bodyObj;
+ }
+}
diff --git a/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderFactory.java b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderFactory.java
new file mode 100644
index 000000000..d744abf35
--- /dev/null
+++ b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderFactory.java
@@ -0,0 +1,113 @@
+package com.alibaba.fastjson2.support.solon;
+
+import com.alibaba.fastjson2.JSONWriter;
+import org.noear.solon.core.handle.Render;
+import org.noear.solon.serialization.StringSerializerRender;
+import org.noear.solon.serialization.prop.JsonProps;
+import org.noear.solon.serialization.prop.JsonPropsUtil;
+
+/**
+ * Json RenderFactory
+ *
+ * @author noear
+ * @author 暮城留风
+ * @since 1.10
+ * @since 2024-10-01
+ */
+public class Fastjson2RenderFactory
+ extends Fastjson2RenderFactoryBase {
+ public Fastjson2RenderFactory(JsonProps jsonProps) {
+ serializer.cfgSerializeFeatures(false, true,
+ JSONWriter.Feature.BrowserCompatible);
+ applyProps(jsonProps);
+ }
+
+ /**
+ * Suffix or name mapping
+ */
+ @Override
+ public String[] mappings() {
+ return new String[]{"@json"};
+ }
+
+ /**
+ * Create Render
+ */
+ @Override
+ public Render create() {
+ return new StringSerializerRender(false, serializer);
+ }
+
+ /**
+ * Resetting features
+ */
+ public void setFeatures(JSONWriter.Feature... features) {
+ serializer.cfgSerializeFeatures(true, true, features);
+ }
+
+ /**
+ * Adding features
+ */
+ public void addFeatures(JSONWriter.Feature... features) {
+ serializer.cfgSerializeFeatures(false, true, features);
+ }
+
+ /**
+ * Removing features
+ */
+ public void removeFeatures(JSONWriter.Feature... features) {
+ serializer.cfgSerializeFeatures(false, false, features);
+ }
+
+ protected void applyProps(JsonProps jsonProps) {
+ if (jsonProps != null && jsonProps.dateAsTicks) {
+ jsonProps.dateAsTicks = false;
+ this.getSerializer().getSerializeConfig()
+ .setDateFormat("millis");
+ }
+
+ if (JsonPropsUtil.apply(this, jsonProps)) {
+ if (jsonProps.longAsString) {
+ this.addFeatures(JSONWriter.Feature.WriteLongAsString);
+ }
+
+ boolean writeNulls = jsonProps.nullAsWriteable ||
+ jsonProps.nullNumberAsZero ||
+ jsonProps.nullArrayAsEmpty ||
+ jsonProps.nullBoolAsFalse ||
+ jsonProps.nullStringAsEmpty;
+
+ if (jsonProps.nullStringAsEmpty) {
+ this.addFeatures(JSONWriter.Feature.WriteNullStringAsEmpty);
+ }
+
+ if (jsonProps.nullBoolAsFalse) {
+ this.addFeatures(JSONWriter.Feature.WriteNullBooleanAsFalse);
+ }
+
+ if (jsonProps.nullNumberAsZero) {
+ this.addFeatures(JSONWriter.Feature.WriteNullNumberAsZero);
+ }
+
+ if (jsonProps.boolAsInt) {
+ this.addFeatures(JSONWriter.Feature.WriteBooleanAsNumber);
+ }
+
+ if (jsonProps.longAsString) {
+ this.addFeatures(JSONWriter.Feature.WriteLongAsString);
+ }
+
+ if (jsonProps.nullArrayAsEmpty) {
+ this.addFeatures(JSONWriter.Feature.WriteNullListAsEmpty);
+ }
+
+ if (jsonProps.enumAsName) {
+ this.addFeatures(JSONWriter.Feature.WriteEnumsUsingName);
+ }
+
+ if (writeNulls) {
+ this.addFeatures(JSONWriter.Feature.WriteNulls);
+ }
+ }
+ }
+}
diff --git a/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderFactoryBase.java b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderFactoryBase.java
new file mode 100644
index 000000000..c82b029be
--- /dev/null
+++ b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderFactoryBase.java
@@ -0,0 +1,78 @@
+package com.alibaba.fastjson2.support.solon;
+
+import com.alibaba.fastjson2.writer.ObjectWriter;
+import com.alibaba.fastjson2.writer.ObjectWriterProvider;
+import org.noear.solon.core.convert.Converter;
+import org.noear.solon.serialization.JsonRenderFactory;
+
+/**
+ * Json RenderFactory Base
+ *
+ * @author noear
+ * @author 暮城留风
+ * @since 1.10
+ * @since 2024-10-01
+ */
+public abstract class Fastjson2RenderFactoryBase
+ implements JsonRenderFactory {
+ protected Fastjson2StringSerializer serializer = new Fastjson2StringSerializer();
+
+ public Fastjson2RenderFactoryBase() {
+ //The default time handling is a timestamp
+ serializer.getSerializeConfig().setDateFormat("millis");
+ }
+
+ /**
+ * Gets the serializer
+ */
+ public Fastjson2StringSerializer getSerializer() {
+ return serializer;
+ }
+
+ /**
+ * Serialize the configuration
+ */
+ public ObjectWriterProvider config() {
+ return serializer.getSerializeConfig().getProvider();
+ }
+
+ /**
+ * Adding the encoder
+ *
+ * @param clz type
+ * @param encoder encoder
+ */
+ public void addEncoder(Class clz, ObjectWriter encoder) {
+ config().register(clz, encoder);
+ }
+
+ /**
+ * Add converter (simplified version of encoder)
+ *
+ * @param clz type
+ * @param converter converter
+ */
+ @Override
+ public void addConvertor(Class clz, Converter converter) {
+ addEncoder(clz, (out, obj, fieldName, fieldType, features) -> {
+ Object val = converter.convert((T) obj);
+ if (val == null) {
+ out.writeNull();
+ } else if (val instanceof String) {
+ out.writeString((String) val);
+ } else if (val instanceof Number) {
+ if (val instanceof Long) {
+ out.writeInt64(((Number) val).longValue());
+ } else if (val instanceof Integer) {
+ out.writeInt32(((Number) val).intValue());
+ } else if (val instanceof Float) {
+ out.writeDouble(((Number) val).floatValue());
+ } else {
+ out.writeDouble(((Number) val).doubleValue());
+ }
+ } else {
+ throw new IllegalArgumentException("The result type of the converter is not supported: " + val.getClass().getName());
+ }
+ });
+ }
+}
diff --git a/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderTypedFactory.java b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderTypedFactory.java
new file mode 100644
index 000000000..a1579d0a4
--- /dev/null
+++ b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2RenderTypedFactory.java
@@ -0,0 +1,41 @@
+package com.alibaba.fastjson2.support.solon;
+
+import com.alibaba.fastjson2.JSONWriter;
+import org.noear.solon.core.handle.Render;
+import org.noear.solon.serialization.StringSerializerRender;
+
+/**
+ * Json Typed RenderFactory (Typically used with RPC)
+ *
+ * @author noear
+ * @author 暮城留风
+ * @since 1.10
+ * @since 2024-10-01
+ */
+public class Fastjson2RenderTypedFactory
+ extends Fastjson2RenderFactoryBase {
+ public Fastjson2RenderTypedFactory() {
+ serializer.cfgSerializeFeatures(false, true,
+ JSONWriter.Feature.BrowserCompatible,
+ JSONWriter.Feature.WriteClassName,
+ JSONWriter.Feature.ReferenceDetection,
+ JSONWriter.Feature.WriteLongAsString
+ );
+ }
+
+ /**
+ * Suffix or name mapping
+ */
+ @Override
+ public String[] mappings() {
+ return new String[]{"@type_json"};
+ }
+
+ /**
+ * Create Render
+ */
+ @Override
+ public Render create() {
+ return new StringSerializerRender(true, serializer);
+ }
+}
diff --git a/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2StringSerializer.java b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2StringSerializer.java
new file mode 100644
index 000000000..f2c69ec4e
--- /dev/null
+++ b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/Fastjson2StringSerializer.java
@@ -0,0 +1,176 @@
+package com.alibaba.fastjson2.support.solon;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONFactory;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.reader.ObjectReaderProvider;
+import com.alibaba.fastjson2.writer.ObjectWriterProvider;
+import org.noear.solon.Utils;
+import org.noear.solon.core.handle.Context;
+import org.noear.solon.core.handle.ModelAndView;
+import org.noear.solon.serialization.ContextSerializer;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+
+/**
+ * Fastjson2 string serialization
+ *
+ * @author noear
+ * @author 暮城留风
+ * @since 1.10
+ * @since 2.8
+ * @since 2024-10-01
+ */
+public class Fastjson2StringSerializer
+ implements ContextSerializer {
+ private static final String label = "/json";
+
+ private JSONWriter.Context serializeConfig;
+ private JSONReader.Context deserializeConfig;
+
+ /**
+ * Get the serialization configuration
+ */
+ public JSONWriter.Context getSerializeConfig() {
+ if (serializeConfig == null) {
+ serializeConfig = new JSONWriter.Context(new ObjectWriterProvider());
+ }
+
+ return serializeConfig;
+ }
+
+ /**
+ * Configure the serialization feature
+ *
+ * @param isReset Reset or not
+ * @param isAdd Add or not
+ * @param features Feature
+ */
+ public void cfgSerializeFeatures(boolean isReset, boolean isAdd, JSONWriter.Feature... features) {
+ if (isReset) {
+ getSerializeConfig().setFeatures(JSONFactory.getDefaultWriterFeatures());
+ }
+
+ for (JSONWriter.Feature feature : features) {
+ getSerializeConfig().config(feature, isAdd);
+ }
+ }
+
+ /**
+ * Get the deserialized configuration
+ */
+ public JSONReader.Context getDeserializeConfig() {
+ if (deserializeConfig == null) {
+ deserializeConfig = new JSONReader.Context(new ObjectReaderProvider());
+ }
+ return deserializeConfig;
+ }
+
+ /**
+ * Configure the deserialization feature
+ *
+ * @param isReset Reset or not
+ * @param isAdd Add or not
+ * @param features Feature
+ */
+ public void cfgDeserializeFeatures(boolean isReset, boolean isAdd, JSONReader.Feature... features) {
+ if (isReset) {
+ getDeserializeConfig().setFeatures(JSONFactory.getDefaultReaderFeatures());
+ }
+
+ for (JSONReader.Feature feature : features) {
+ getDeserializeConfig().config(feature, isAdd);
+ }
+ }
+
+ /**
+ * Getting the content type
+ */
+ @Override
+ public String getContentType() {
+ return "application/json";
+ }
+
+ /**
+ * Match or not
+ *
+ * @param ctx Handling context
+ * @param mime content type
+ */
+ @Override
+ public boolean matched(Context ctx, String mime) {
+ if (mime == null) {
+ return false;
+ } else {
+ return mime.contains(label);
+ }
+ }
+
+ /**
+ * Serializer name
+ */
+ @Override
+ public String name() {
+ return "fastjson2-json";
+ }
+
+ /**
+ * Serialize
+ *
+ * @param obj object
+ */
+ @Override
+ public String serialize(Object obj) throws IOException {
+ return JSON.toJSONString(obj, getSerializeConfig());
+ }
+
+ /**
+ * Deserialize
+ *
+ * @param data data
+ * @param toType Target type
+ */
+ @Override
+ public Object deserialize(String data, Type toType) throws IOException {
+ if (toType == null) {
+ return JSON.parse(data, getDeserializeConfig());
+ } else {
+ return JSON.parseObject(data, toType, getDeserializeConfig());
+ }
+ }
+
+ /**
+ * Serialize the body
+ *
+ * @param ctx Handling context
+ * @param data data
+ */
+ @Override
+ public void serializeToBody(Context ctx, Object data) throws IOException {
+ ctx.contentType(getContentType());
+
+ if (data instanceof ModelAndView) {
+ ctx.output(serialize(((ModelAndView) data).model()));
+ } else {
+ ctx.output(serialize(data));
+ }
+ }
+
+ /**
+ * Deserialize the body
+ *
+ * @param ctx Handling context
+ */
+ @Override
+ public Object deserializeFromBody(Context ctx) throws IOException {
+ String data = ctx.bodyNew();
+
+ if (Utils.isNotEmpty(data)) {
+ return JSON.parse(data, getDeserializeConfig());
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/integration/Fastjson2Plugin.java b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/integration/Fastjson2Plugin.java
new file mode 100644
index 000000000..8c8a90c2b
--- /dev/null
+++ b/extension-solon/src/main/java/com/alibaba/fastjson2/support/solon/integration/Fastjson2Plugin.java
@@ -0,0 +1,37 @@
+package com.alibaba.fastjson2.support.solon.integration;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2ActionExecutor;
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderTypedFactory;
+import org.noear.solon.Solon;
+import org.noear.solon.core.AppContext;
+import org.noear.solon.core.Plugin;
+import org.noear.solon.serialization.prop.JsonProps;
+
+/**
+ * Fastjson2 for solon extension integration plugin
+ *
+ * @author noear
+ * */
+public class Fastjson2Plugin
+ implements Plugin {
+ @Override
+ public void start(AppContext context) {
+ JsonProps jsonProps = JsonProps.create(context);
+
+ //::renderFactory
+ Fastjson2RenderFactory renderFactory = new Fastjson2RenderFactory(jsonProps); //绑定属性
+ context.wrapAndPut(Fastjson2RenderFactory.class, renderFactory); //推入容器,用于扩展
+ Solon.app().renderManager().register(renderFactory);
+
+ //::renderTypedFactory
+ Fastjson2RenderTypedFactory renderTypedFactory = new Fastjson2RenderTypedFactory();
+ context.wrapAndPut(Fastjson2RenderTypedFactory.class, renderTypedFactory); //推入容器,用于扩展
+ Solon.app().renderManager().register(renderTypedFactory);
+
+ //::actionExecutor
+ Fastjson2ActionExecutor actionExecutor = new Fastjson2ActionExecutor(); //支持 json 内容类型执行
+ context.wrapAndPut(Fastjson2ActionExecutor.class, actionExecutor); //推入容器,用于扩展
+ Solon.app().chainManager().addExecuteHandler(actionExecutor);
+ }
+}
diff --git a/extension-solon/src/main/resources/META-INF/solon/fastjson2-extension-solon.properties b/extension-solon/src/main/resources/META-INF/solon/fastjson2-extension-solon.properties
new file mode 100644
index 000000000..85d79f712
--- /dev/null
+++ b/extension-solon/src/main/resources/META-INF/solon/fastjson2-extension-solon.properties
@@ -0,0 +1,2 @@
+solon.plugin=com.alibaba.fastjson2.support.solon.integration.Fastjson2Plugin
+solon.plugin.priority=18
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/CustomDateDo.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/CustomDateDo.java
new file mode 100644
index 000000000..f0425be6a
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/CustomDateDo.java
@@ -0,0 +1,19 @@
+package com.alibaba.fastjson2.support.solon.test._model;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * @author noear 2024/9/4 created
+ */
+@Setter
+@Getter
+public class CustomDateDo {
+ private Date date;
+
+ @JSONField(format = "yyyy-MM-dd HH:mm:ss")
+ private Date date2;
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/OrderDo.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/OrderDo.java
new file mode 100644
index 000000000..8ec342f94
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/OrderDo.java
@@ -0,0 +1,16 @@
+package com.alibaba.fastjson2.support.solon.test._model;
+
+/**
+ * @author noear 2023/8/16 created
+ */
+public class OrderDo {
+ long orderId = 2;
+
+ public void setOrderId(long orderId) {
+ this.orderId = orderId;
+ }
+
+ public long getOrderId() {
+ return orderId;
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/UserDo.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/UserDo.java
new file mode 100644
index 000000000..0fd9a75c9
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/_model/UserDo.java
@@ -0,0 +1,34 @@
+package com.alibaba.fastjson2.support.solon.test._model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author noear 2023/1/16 created
+ */
+@Getter
+@Setter
+public class UserDo
+ implements Serializable {
+ String s0;
+
+ String s1 = "noear";
+
+ Boolean b0;
+ boolean b1 = true;
+
+ Long n0;
+ Long n1 = 1L;
+
+ Double d0;
+ Double d1 = 1.0D;
+
+ Object obj0;
+ List list0;
+ Map map0;
+ Map map1;
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/action/ExecutorTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/action/ExecutorTest.java
new file mode 100644
index 000000000..8d68f1549
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/action/ExecutorTest.java
@@ -0,0 +1,56 @@
+package com.alibaba.fastjson2.support.solon.test.action;
+
+import org.junit.jupiter.api.Test;
+import org.noear.solon.Solon;
+import org.noear.solon.annotation.Controller;
+import org.noear.solon.annotation.Mapping;
+import org.noear.solon.annotation.Param;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+/**
+ * @author noear 2024/9/17 created
+ */
+@SolonTest
+public class ExecutorTest {
+ @Test
+ public void test() throws Throwable {
+ ContextEmpty ctx = new ContextEmpty();
+ ctx.headerMap().add("Content-Type", "text/json");
+ ctx.pathNew("/a1");
+ ctx.bodyNew("{\"name\":\"noear\",\"label\":\"A\"}");
+
+ Solon.app().tryHandle(ctx);
+ ctx.result = ctx.attr("output");
+ System.out.println(ctx.result);
+ assert "Hello noear A".equals(ctx.result);
+
+ ctx = new ContextEmpty();
+ ctx.headerMap().add("Content-Type", "text/json");
+ ctx.pathNew("/a2");
+ ctx.bodyNew("{\"name\":\"noear\",\"label\":\"A\"}");
+
+ Solon.app().tryHandle(ctx);
+ ctx.result = ctx.attr("output");
+ System.out.println(ctx.result);
+ assert "\"A\"".equals(ctx.result);
+ }
+
+ @Controller
+ public static class Demo {
+ @Mapping("/a1")
+ public String a1(@Param("name") String name, @Param("label") Label label) {
+ return "Hello " + name + " " + label;
+ }
+
+ @Mapping("/a2")
+ public Label a2(@Param("name") String name, @Param("label") Label label) {
+ return label;
+ }
+ }
+
+ public enum Label {
+ A,
+ B
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test0/QuickConfigTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test0/QuickConfigTest.java
new file mode 100644
index 000000000..0acbf332a
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test0/QuickConfigTest.java
@@ -0,0 +1,47 @@
+package com.alibaba.fastjson2.support.solon.test.config.test0;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.UserDo;
+import org.junit.jupiter.api.Test;
+import org.noear.snack.ONode;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author noear 2023/1/16 created
+ */
+@Import(profiles = "classpath:features2_test0.yml")
+@SolonTest
+public class QuickConfigTest {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ UserDo userDo = new UserDo();
+
+ Map data = new HashMap<>();
+ data.put("time", new Date(1673861993477L));
+ data.put("long", 12L);
+ data.put("int", 12);
+ data.put("null", null);
+
+ userDo.setMap1(data);
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(userDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ assert ONode.load(output).count() == 5;
+
+ assert "{\"b1\":true,\"d1\":1.0,\"map1\":{\"time\":1673861993477,\"long\":12,\"int\":12},\"n1\":1,\"s1\":\"noear\"}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1/QuickConfigTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1/QuickConfigTest.java
new file mode 100644
index 000000000..67c04db8c
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1/QuickConfigTest.java
@@ -0,0 +1,48 @@
+package com.alibaba.fastjson2.support.solon.test.config.test1;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.UserDo;
+import org.junit.jupiter.api.Test;
+import org.noear.snack.ONode;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author noear 2023/1/16 created
+ */
+@Import(profiles = "classpath:features2_test1.yml")
+@SolonTest
+public class QuickConfigTest {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ UserDo userDo = new UserDo();
+
+ Map data = new HashMap<>();
+ data.put("time", new Date(1673861993477L));
+ data.put("long", 12L);
+ data.put("int", 12);
+ data.put("null", null);
+
+ userDo.setMap1(data);
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(userDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ assert ONode.load(output).count() == 5;
+
+ //完美
+ assert "{\"b1\":true,\"d1\":1.0,\"map1\":{\"time\":\"2023-01-16 17:39:53\",\"long\":12,\"int\":12},\"n1\":1,\"s1\":\"noear\"}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1/QuickConfigTest2.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1/QuickConfigTest2.java
new file mode 100644
index 000000000..2b878c2b2
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1/QuickConfigTest2.java
@@ -0,0 +1,48 @@
+package com.alibaba.fastjson2.support.solon.test.config.test1;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.UserDo;
+import org.junit.jupiter.api.Test;
+import org.noear.snack.ONode;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 只对时间进行格式化
+ */
+@Import(profiles = "classpath:features2_test1-2.yml")
+@SolonTest
+public class QuickConfigTest2 {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ UserDo userDo = new UserDo();
+
+ Map data = new HashMap<>();
+ data.put("time", new Date(1673861993477L));
+ data.put("long", 12L);
+ data.put("int", 12);
+ data.put("null", null);
+
+ userDo.setMap1(data);
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(userDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ assert ONode.load(output).count() == 5;
+
+ //完美
+ assert "{\"b1\":true,\"d1\":1.0,\"map1\":{\"time\":1673861993477,\"long\":12,\"int\":12},\"n1\":1,\"s1\":\"noear\"}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1_2/FormatTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1_2/FormatTest.java
new file mode 100644
index 000000000..cd6d9071e
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1_2/FormatTest.java
@@ -0,0 +1,60 @@
+package com.alibaba.fastjson2.support.solon.test.config.test1_2;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.alibaba.fastjson2.writer.ObjectWriter;
+
+import java.lang.reflect.Type;
+import java.util.Date;
+
+/**
+ * @author noear 2024/9/4 created
+ */
+public class FormatTest {
+ public static void main(String[] args) {
+ //
+ // 当有 Provider::register 类型处理后,@JSONField 注解失效了
+ //
+ JSONWriter.Context context = new JSONWriter.Context();
+ context.getProvider().register(Date.class, new ObjectWriter() {
+ @Override
+ public void write(JSONWriter jsonWriter, Object o, Object o1, Type type, long l) {
+ jsonWriter.writeInt64(((Date) o).getTime());
+ }
+ });
+
+ CustomDateDo dateDo = new CustomDateDo();
+
+ dateDo.setDate(new Date(1673861993477L));
+ dateDo.setDate2(new Date(1673861993477L));
+
+ String json = JSON.toJSONString(dateDo, context);
+ System.out.println(json); //{"date":1673861993477,"date2":1673861993477}
+
+ assert "{\"date\":1673861993477,\"date2\":\"2023-01-16 17:39:53\"}".equals(json);
+ }
+
+ public static class CustomDateDo {
+ private Date date;
+
+ @JSONField(format = "yyyy-MM-dd HH:mm:ss")
+ private Date date2;
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public void setDate2(Date date2) {
+ this.date2 = date2;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public Date getDate2() {
+ return date2;
+ }
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1_2/QuickConfigTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1_2/QuickConfigTest.java
new file mode 100644
index 000000000..452fed7f7
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test1_2/QuickConfigTest.java
@@ -0,0 +1,38 @@
+package com.alibaba.fastjson2.support.solon.test.config.test1_2;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.CustomDateDo;
+import org.junit.jupiter.api.Test;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+
+/**
+ * 时间进行格式化 + long,int 转为字符串 + 常见类型转为非null + 所有null输出
+ */
+@Import(profiles = "classpath:features2_test1-2.yml")
+@SolonTest
+public class QuickConfigTest {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ CustomDateDo dateDo = new CustomDateDo();
+
+ dateDo.setDate(new Date(1673861993477L));
+ dateDo.setDate2(new Date(1673861993477L));
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(dateDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ //err: register 类型处理后,JSONField 失效了
+ assert "{\"date\":1673861993477,\"date2\":\"2023-01-16 17:39:53\"}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test2/QuickConfigTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test2/QuickConfigTest.java
new file mode 100644
index 000000000..7c28a3db6
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test2/QuickConfigTest.java
@@ -0,0 +1,65 @@
+package com.alibaba.fastjson2.support.solon.test.config.test2;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.OrderDo;
+import com.alibaba.fastjson2.support.solon.test._model.UserDo;
+import org.junit.jupiter.api.Test;
+import org.noear.snack.ONode;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author noear 2023/1/16 created
+ */
+@Import(profiles = "classpath:features2_test2.yml")
+@SolonTest
+public class QuickConfigTest {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ UserDo userDo = new UserDo();
+
+ Map data = new HashMap<>();
+ data.put("time", new Date(1673861993477L));
+ data.put("long", 12L);
+ data.put("int", 12);
+ data.put("null", null);
+
+ userDo.setMap1(data);
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(userDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ assert ONode.load(output).count() == 5;
+
+ //error: int 没转为 string
+ assert "{\"b1\":1,\"d1\":1.0,\"map1\":{\"time\":\"2023-01-16 17:39:53\",\"long\":\"12\",\"int\":12},\"n1\":\"1\",\"s1\":\"noear\"}".equals(output);
+ }
+
+ @Test
+ public void hello3() throws Throwable {
+ Map data = new LinkedHashMap<>();
+ data.put("long", 1L);
+ data.put("order", new OrderDo());
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(data, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ assert "{\"long\":\"1\",\"order\":{\"orderId\":\"2\"}}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test3/QuickConfigTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test3/QuickConfigTest.java
new file mode 100644
index 000000000..c2e61a22f
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test3/QuickConfigTest.java
@@ -0,0 +1,45 @@
+package com.alibaba.fastjson2.support.solon.test.config.test3;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.UserDo;
+import org.junit.jupiter.api.Test;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author noear 2023/1/16 created
+ */
+@Import(profiles = "classpath:features2_test3.yml")
+@SolonTest
+public class QuickConfigTest {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ UserDo userDo = new UserDo();
+
+ Map data = new HashMap<>();
+ data.put("time", new Date(1673861993477L));
+ data.put("long", 12L);
+ data.put("int", 12);
+ data.put("null", null);
+
+ userDo.setMap1(data);
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(userDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ //error: int 没转为 string
+ assert "{\"b0\":0,\"b1\":1,\"d0\":0,\"d1\":1.0,\"list0\":[],\"map0\":null,\"map1\":{\"null\":null,\"time\":\"2023-01-16 17:39:53\",\"long\":\"12\",\"int\":12},\"n0\":\"0\",\"n1\":\"1\",\"obj0\":null,\"s0\":\"\",\"s1\":\"noear\"}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test4/QuickConfigTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test4/QuickConfigTest.java
new file mode 100644
index 000000000..c291fcab2
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/config/test4/QuickConfigTest.java
@@ -0,0 +1,45 @@
+package com.alibaba.fastjson2.support.solon.test.config.test4;
+
+import com.alibaba.fastjson2.support.solon.Fastjson2RenderFactory;
+import com.alibaba.fastjson2.support.solon.test._model.UserDo;
+import org.junit.jupiter.api.Test;
+import org.noear.solon.annotation.Import;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.ContextEmpty;
+import org.noear.solon.test.SolonTest;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author noear 2023/1/16 created
+ */
+@Import(profiles = "classpath:features2_test4.yml")
+@SolonTest
+public class QuickConfigTest {
+ @Inject
+ Fastjson2RenderFactory renderFactory;
+
+ @Test
+ public void hello2() throws Throwable {
+ UserDo userDo = new UserDo();
+
+ Map data = new HashMap<>();
+ data.put("time", new Date(1673861993477L));
+ data.put("long", 12L);
+ data.put("int", 12);
+ data.put("null", null);
+
+ userDo.setMap1(data);
+
+ ContextEmpty ctx = new ContextEmpty();
+ renderFactory.create().render(userDo, ctx);
+ String output = ctx.attr("output");
+
+ System.out.println(output);
+
+ //error: int 没转为 string
+ assert "{\"b0\":0,\"b1\":1,\"d0\":0,\"d1\":1.0,\"list0\":[],\"map0\":null,\"map1\":{\"null\":null,\"time\":\"2023-01-16 17:39:53\",\"long\":\"12\",\"int\":12},\"n0\":\"0\",\"n1\":\"1\",\"obj0\":null,\"s0\":\"\",\"s1\":\"noear\"}".equals(output);
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest.java
new file mode 100644
index 000000000..0661ccaa0
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest.java
@@ -0,0 +1,26 @@
+package com.alibaba.fastjson2.support.solon.test.requirement.case1;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author noear 2023/10/29 created
+ */
+public class AsNumberTest {
+ @Test
+ public void test() throws Exception {
+ String json = JSON.toJSONString(new Bean(),
+ JSONWriter.Feature.WriteNullNumberAsZero,
+ JSONWriter.Feature.WriteLongAsString);
+
+ System.out.println(json);
+ assertEquals("{\"value\":\"0\"}", json);
+ }
+
+ public static class Bean {
+ public Long value;
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest2.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest2.java
new file mode 100644
index 000000000..7277b6fdc
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest2.java
@@ -0,0 +1,41 @@
+package com.alibaba.fastjson2.support.solon.test.requirement.case1;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.writer.ObjectWriterProvider;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author noear 2023/10/29 created
+ */
+public class AsNumberTest2 {
+ @Test
+ public void test() throws Exception {
+ ObjectWriterProvider writerProvider = new ObjectWriterProvider();
+
+ JSONWriter.Context writeContext = new JSONWriter.Context(writerProvider,
+ JSONWriter.Feature.WriteNullNumberAsZero);
+
+ Demo demo = new Demo();
+ String tmp = JSON.toJSONString(demo, writeContext);
+ System.out.println(tmp);
+
+ assert "{\"a\":0,\"b\":0,\"c\":1}".equals(tmp);
+
+ writeContext = new JSONWriter.Context(writerProvider,
+ JSONWriter.Feature.WriteNullNumberAsZero,
+ JSONWriter.Feature.WriteLongAsString);
+
+ demo = new Demo();
+ tmp = JSON.toJSONString(demo, writeContext);
+ System.out.println(tmp);
+
+ assert "{\"a\":\"0\",\"b\":\"0\",\"c\":\"1\"}".equals(tmp);
+ }
+
+ public static class Demo {
+ public long a;
+ public Long b;
+ public Long c = 1L;
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest3.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest3.java
new file mode 100644
index 000000000..9a4c1c952
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case1/AsNumberTest3.java
@@ -0,0 +1,26 @@
+package com.alibaba.fastjson2.support.solon.test.requirement.case1;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author noear 2023/10/29 created
+ */
+public class AsNumberTest3 {
+ @Test
+ public void test() throws Exception {
+ String json = JSON.toJSONString(new Bean(),
+ JSONWriter.Feature.WriteNullBooleanAsFalse,
+ JSONWriter.Feature.WriteBooleanAsNumber);
+
+ System.out.println(json);
+ assertEquals("{\"value\":0}", json);
+ }
+
+ public static class Bean {
+ public Boolean value;
+ }
+}
diff --git a/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case2/TypeTest.java b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case2/TypeTest.java
new file mode 100644
index 000000000..a2ba91212
--- /dev/null
+++ b/extension-solon/src/test/java/com/alibaba/fastjson2/support/solon/test/requirement/case2/TypeTest.java
@@ -0,0 +1,25 @@
+package com.alibaba.fastjson2.support.solon.test.requirement.case2;
+
+/**
+ * @author noear 2023/10/29 created
+ */
+public class TypeTest {
+// @Test
+// public void test() throws Throwable {
+// Bean data = new Bean();
+// data.value = 12L;
+//
+// String output = JSON.toJSONString(data, JSONWriter.Feature.WriteClassName);
+//
+// System.out.println(output); //{"@type":"features.type0.TypeTest$Bean","value":12L}
+// assertEquals("{\"@type\":\"features.type0.TypeTest$Bean\",\"value\":12}", output);
+// }
+
+ public static class Bean {
+ private Long value;
+
+ public Long getValue() {
+ return value;
+ }
+ }
+}
diff --git a/extension-solon/src/test/resources/features2_test0.yml b/extension-solon/src/test/resources/features2_test0.yml
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/extension-solon/src/test/resources/features2_test0.yml
@@ -0,0 +1 @@
+
diff --git a/extension-solon/src/test/resources/features2_test1-2.yml b/extension-solon/src/test/resources/features2_test1-2.yml
new file mode 100644
index 000000000..00e4125fe
--- /dev/null
+++ b/extension-solon/src/test/resources/features2_test1-2.yml
@@ -0,0 +1,4 @@
+
+
+solon.serialization.json:
+ dateAsTicks: true #将date转为毫秒数
\ No newline at end of file
diff --git a/extension-solon/src/test/resources/features2_test1.yml b/extension-solon/src/test/resources/features2_test1.yml
new file mode 100644
index 000000000..7022f983a
--- /dev/null
+++ b/extension-solon/src/test/resources/features2_test1.yml
@@ -0,0 +1,5 @@
+
+
+solon.serialization.json:
+ dateAsFormat: 'yyyy-MM-dd HH:mm:ss' #配置日期格式(默认输出为时间戳)
+ dateAsTimeZone: 'GMT+8' #配置时区
\ No newline at end of file
diff --git a/extension-solon/src/test/resources/features2_test2.yml b/extension-solon/src/test/resources/features2_test2.yml
new file mode 100644
index 000000000..525c66b49
--- /dev/null
+++ b/extension-solon/src/test/resources/features2_test2.yml
@@ -0,0 +1,13 @@
+
+
+solon.serialization.json:
+ dateAsFormat: 'yyyy-MM-dd HH:mm:ss' #配置日期格式(默认输出为时间戳)
+ dateAsTimeZone: 'GMT+8' #配置时区
+ longAsString: true #将long型转为字符串输出 (默认为false)
+ intAsString: true #将int型转为字符串输出 (默认为false)
+ boolAsInt: true #将bool型转为字符串输出 (默认为false)
+ nullStringAsEmpty: false
+ nullBoolAsFalse: false
+ nullNumberAsZero: false
+ nullArrayAsEmpty: false
+ nullAsWriteable: false
\ No newline at end of file
diff --git a/extension-solon/src/test/resources/features2_test3.yml b/extension-solon/src/test/resources/features2_test3.yml
new file mode 100644
index 000000000..3ee4a8e62
--- /dev/null
+++ b/extension-solon/src/test/resources/features2_test3.yml
@@ -0,0 +1,13 @@
+
+
+solon.serialization.json:
+ dateAsFormat: 'yyyy-MM-dd HH:mm:ss' #配置日期格式(默认输出为时间戳)
+ dateAsTimeZone: 'GMT+8' #配置时区
+ longAsString: true #将long型转为字符串输出 (默认为false)
+ intAsString: true #将int型转为字符串输出 (默认为false)
+ boolAsInt: true #将bool型转为字符串输出 (默认为false)
+ nullStringAsEmpty: true
+ nullBoolAsFalse: true
+ nullNumberAsZero: true
+ nullArrayAsEmpty: true
+ nullAsWriteable: false
\ No newline at end of file
diff --git a/extension-solon/src/test/resources/features2_test4.yml b/extension-solon/src/test/resources/features2_test4.yml
new file mode 100644
index 000000000..7e0123907
--- /dev/null
+++ b/extension-solon/src/test/resources/features2_test4.yml
@@ -0,0 +1,13 @@
+
+
+solon.serialization.json:
+ dateAsFormat: 'yyyy-MM-dd HH:mm:ss' #配置日期格式(默认输出为时间戳)
+ dateAsTimeZone: 'GMT+8' #配置时区
+ longAsString: true #将long型转为字符串输出 (默认为false)
+ intAsString: true #将int型转为字符串输出 (默认为false)
+ boolAsInt: true #将bool型转为字符串输出 (默认为false)
+ nullStringAsEmpty: true
+ nullBoolAsFalse: true
+ nullNumberAsZero: true
+ nullArrayAsEmpty: true
+ nullAsWriteable: true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 31930839c..56680fb36 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
2.17.2
11.0.15
2.44
+ 3.0.1
5.3.31
6.1.3
5.8.8
@@ -71,11 +72,13 @@
+ example-solon-test
example-spring-test
extension
+ extension-solon
extension-spring5