Repository: alibaba/fastjson2
Pull request #3591 (2025-06-16 19:58:36)

Bump org.apache.maven.plugins:maven-clean-plugin from 3.4.1 to 3.5.0
Bumps [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin) from 3.4.1 to 3.5.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/apache/maven-clean-plugin/releases">org.apache.maven.plugins:maven-clean-plugin's releases</a>.</em></p>
<blockquote>
<h2>3.5.0</h2>
<!-- raw HTML omitted -->
<h2>🚀 New features and improvements</h2>
<ul>
<li>Configuration parameter for deleting read-only files (<a href="https://redirect.github.com/apache/maven-clean-plugin/pull/252">#252</a>) <a href="https://github.com/desruisseaux"><code>@​desruisseaux</code></a></li>
</ul>
<h2>👻 Maintenance</h2>
<ul>
<li>Bump project to 3.5.0-SNAPSHOT version (<a href="https://redirect.github.com/apache/maven-clean-plugin/pull/254">#254</a>) <a href="https://github.com/slawekjaranowski"><code>@​slawekjaranowski</code></a></li>
<li>Update README in 3.x branch (<a href="https://redirect.github.com/apache/maven-clean-plugin/pull/249">#249</a>) <a href="https://github.com/slawekjaranowski"><code>@​slawekjaranowski</code></a></li>
</ul>
<h2>📦 Dependency updates</h2>
<ul>
<li>Bump org.apache.maven.resolver:maven-resolver-api from 1.9.22 to 1.9.23 (<a href="https://redirect.github.com/apache/maven-clean-plugin/pull/251">#251</a>) @<a href="https://github.com/apps/dependabot">dependabot[bot]</a></li>
<li>Bump org.codehaus.plexus:plexus-testing from 1.4.0 to 1.5.0 (<a href="https://redirect.github.com/apache/maven-clean-plugin/pull/244">#244</a>) @<a href="https://github.com/apps/dependabot">dependabot[bot]</a></li>
<li>Bump org.apache.maven.plugins:maven-plugins from 43 to 44 (<a href="https://redirect.github.com/apache/maven-clean-plugin/pull/237">#237</a>) @<a href="https://github.com/apps/dependabot">dependabot[bot]</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/d18c59617e3add4d964ba51085b49bec88da556e"><code>d18c596</code></a> [maven-release-plugin] prepare release maven-clean-plugin-3.5.0</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/51178853964d6a0a88386aaaf43b1118e136e7ef"><code>5117885</code></a> Bump project to 3.5.0-SNAPSHOT version</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/7350dbebdad88eacc7fd5fb0e1f9cb7b083902a1"><code>7350dbe</code></a> Configuration parameter for deleting read-only files</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/bdf8c5db828128cff0f3295147c2a4debeb10aa9"><code>bdf8c5d</code></a> Bump org.apache.maven.resolver:maven-resolver-api from 1.9.22 to 1.9.23 (<a href="https://redirect.github.com/apache/maven-clean-plugin/issues/251">#251</a>)</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/d720e18eca9583639842aef52863c9625fea0353"><code>d720e18</code></a> Update README in 3.x branch</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/6c0745c7b0a463951a4b0df2f22728e7641d213b"><code>6c0745c</code></a> Bump org.codehaus.plexus:plexus-testing from 1.4.0 to 1.5.0</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/5dedab5d406f9824036fd4b8de00fddab9aedd08"><code>5dedab5</code></a> fix</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/c22ab2defb461fd1f38c22d0c77d77f8fddcfd7b"><code>c22ab2d</code></a> Bump org.apache.maven.plugins:maven-plugins from 43 to 44</li>
<li><a href="https://github.com/apache/maven-clean-plugin/commit/68f2009a9b284e7c5f4afdd9aef4541065f8e742"><code>68f2009</code></a> [maven-release-plugin] prepare for next development iteration</li>
<li>See full diff in <a href="https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.4.1...maven-clean-plugin-3.5.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-clean-plugin&package-manager=maven&previous-version=3.4.1&new-version=3.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/README.md b/README.md
index e46313316..e49294c52 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 [![Java support](https://img.shields.io/badge/Java-8+-green?logo=java&logoColor=white)](https://openjdk.java.net/)
 [![License](https://img.shields.io/github/license/alibaba/fastjson2?color=4D7A97&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0.html)
 [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-green?label=gitpod&logo=gitpod&logoColor=white)](https://gitpod.io/#https://github.com/alibaba/fastjson2)
-[![Last SNAPSHOT](https://img.shields.io/nexus/snapshots/https/oss.sonatype.org/com.alibaba.fastjson2/fastjson2?label=latest%20snapshot)](https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/)
+[![Last SNAPSHOT](https://img.shields.io/nexus/snapshots/https/central.sonatype.org/com.alibaba.fastjson2/fastjson2?label=latest%20snapshot)](https://central.sonatype.com/repository/maven-snapshots/com/alibaba/fastjson2/fastjson2/maven-metadata.xml)
 [![GitHub Stars](https://img.shields.io/github/stars/alibaba/fastjson2)](https://github.com/alibaba/fastjson2/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/alibaba/fastjson2)](https://github.com/alibaba/fastjson2/fork)
 [![user repos](https://badgen.net/github/dependents-repo/alibaba/fastjson2?label=user%20repos)](https://github.com/alibaba/fastjson2/network/dependents)
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONLargeObjectException.java b/core/src/main/java/com/alibaba/fastjson2/JSONLargeObjectException.java
new file mode 100644
index 000000000..e374266a6
--- /dev/null
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONLargeObjectException.java
@@ -0,0 +1,17 @@
+package com.alibaba.fastjson2;
+
+/**
+ * Exception thrown when attempting to serialize an object that exceeds size limits
+ * and the LargeObject feature is not enabled.
+ *
+ * @since 2.0.58
+ */
+public class JSONLargeObjectException
+        extends JSONException {
+    public JSONLargeObjectException(String message) {
+        super(message);
+    }
+    public JSONLargeObjectException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
index ead8e706e..c52eff0fc 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
@@ -2646,7 +2646,11 @@ final class JSONReaderUTF16
                         result = 1; // invalid
                     }
                 } else {
-                    if (fc != '-' && doubleValue != 0) {
+                    if (fc != '-') {
+                        if (doubleValue != 0) {
+                            doubleValue = -doubleValue;
+                        }
+                    } else if (result == 0) {
                         doubleValue = -doubleValue;
                     }
                 }
@@ -2818,7 +2822,11 @@ final class JSONReaderUTF16
                         result = 1; // invalid
                     }
                 } else {
-                    if (fc != '-' && floatValue != 0) {
+                    if (fc != '-') {
+                        if (floatValue != 0) {
+                            floatValue = -floatValue;
+                        }
+                    } else if (result == 0) {
                         floatValue = -floatValue;
                     }
                 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
index c49b141dc..d00c5441c 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
@@ -3705,7 +3705,11 @@ class JSONReaderUTF8
                         result = 1; // invalid
                     }
                 } else {
-                    if (fc != '-' && doubleValue != 0) {
+                    if (fc != '-') {
+                        if (doubleValue != 0) {
+                            doubleValue = -doubleValue;
+                        }
+                    } else if (result == 0) {
                         doubleValue = -doubleValue;
                     }
                 }
@@ -3877,7 +3881,11 @@ class JSONReaderUTF8
                         result = 1; // invalid
                     }
                 } else {
-                    if (fc != '-' && floatValue != 0) {
+                    if (fc != '-') {
+                        if (floatValue != 0) {
+                            floatValue = -floatValue;
+                        }
+                    } else if (result == 0) {
                         floatValue = -floatValue;
                     }
                 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
index f4316bf59..06fff5264 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
@@ -2728,7 +2728,8 @@ public abstract class JSONWriter
             if (minCapacity < maxArraySize) {
                 newCapacity = maxArraySize;
             } else {
-                throw new OutOfMemoryError("try enabling LargeObject feature instead");
+                throw new JSONLargeObjectException("Maximum array size exceeded. Try enabling LargeObject feature instead. "
+                        + "Requested size: " + minCapacity + ", max size: " + maxArraySize);
             }
         }
         return newCapacity;
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 f139c7e7a..0c2d3a9e0 100644
--- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
+++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
@@ -2176,6 +2176,8 @@ public class ObjectReaderBaseModule
                     case "com.google.common.collect.ImmutableSet":
                     case "com.google.common.collect.SingletonImmutableSet":
                         return ObjectReaderImplList.of(type, null, 0);
+                    case "cn.hutool.core.lang.tree.Tree":
+                        return ObjectReaderImplMap.of(null, (Class<?>) rawType, 0L);
                 }
 
                 if (rawType == Optional.class) {
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 0a7d6a8b5..a29c83b41 100644
--- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java
+++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java
@@ -53,7 +53,6 @@ import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -224,6 +223,8 @@ public class ObjectReaderCreator {
 
             ObjectReader initReader = getInitReader(provider, parameter.getParameterizedType(), parameter.getType(), fieldInfo);
             Type paramType = parameter.getParameterizedType();
+            Type resolvedType = BeanUtils.resolve(objectType, objectClass, paramType);
+            paramType = resolvedType != null ? resolvedType : paramType;
             fieldReaders.add(
                     createFieldReaderParam(
                     null,
@@ -2568,9 +2569,7 @@ public class ObjectReaderCreator {
             }
         }
 
-        boolean list = fieldClass == List.class
-                || fieldClass == ArrayList.class
-                || fieldClass == LinkedList.class
+        boolean list = List.class.isAssignableFrom(fieldClass)
                 || "cn.hutool.json.JSONArray".equals(fieldClass.getName());
         if (list) {
             if (fieldTypeResolved instanceof ParameterizedType) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java
index 347e2363a..deb4d782f 100644
--- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java
+++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java
@@ -888,8 +888,8 @@ public class ObjectReaderProvider
     }
 
     public void getFieldInfo(FieldInfo fieldInfo, Class objectClass, Method method) {
-        for (ObjectReaderModule module : modules) {
-            ObjectReaderAnnotationProcessor annotationProcessor = module.getAnnotationProcessor();
+        for (int i = 0; i < modules.size(); i++) {
+            ObjectReaderAnnotationProcessor annotationProcessor = modules.get(i).getAnnotationProcessor();
             if (annotationProcessor == null) {
                 continue;
             }
diff --git a/core/src/main/java/com/alibaba/fastjson2/util/IOUtils.java b/core/src/main/java/com/alibaba/fastjson2/util/IOUtils.java
index 1750dc9a5..f5eeb9880 100644
--- a/core/src/main/java/com/alibaba/fastjson2/util/IOUtils.java
+++ b/core/src/main/java/com/alibaba/fastjson2/util/IOUtils.java
@@ -61,7 +61,6 @@ public class IOUtils {
     };
 
     private static final short ZERO_DOT_LATIN1;
-    private static final int ZERO_DOT_UTF16;
 
     static {
         short[] shorts = new short[]{
@@ -105,7 +104,6 @@ public class IOUtils {
             DIGITS_K_64[i] = c0 + v;
         }
         ZERO_DOT_LATIN1 = UNSAFE.getShort(new byte[] {'0', '.'}, ARRAY_BYTE_BASE_OFFSET);
-        ZERO_DOT_UTF16 = UNSAFE.getInt(new char[] {'0', '.'}, ARRAY_CHAR_BASE_OFFSET);
     }
 
     private static short digitPair(int value) {
@@ -343,11 +341,11 @@ public class IOUtils {
             int unscaleValSize = stringSize(unscaledVal);
             int insertionPoint = unscaleValSize - scale;
             if (insertionPoint == 0) {
-                putIntUnaligned(buf, off, ZERO_DOT_UTF16);
-                off += 2;
+                buf[off++] = '0';
+                buf[off++] = '.';
             } else if (insertionPoint < 0) {
-                putIntUnaligned(buf, off, ZERO_DOT_UTF16);
-                off += 2;
+                buf[off++] = '0';
+                buf[off++] = '.';
 
                 for (int i = 0; i < -insertionPoint; i++) {
                     putChar(buf, off++, '0');
@@ -485,9 +483,9 @@ public class IOUtils {
         return true;
     }
 
-    public static boolean isNumber(char[] chars, int off, int len) {
+    public static boolean isNumber(char[] buf, int off, int len) {
         for (int i = off, end = off + len; i < end; ++i) {
-            char ch = chars[i];
+            char ch = buf[i];
             if (ch == '+' || ch == '-') {
                 if (i != 0) {
                     return false;
@@ -499,9 +497,9 @@ public class IOUtils {
         return true;
     }
 
-    public static boolean isNumber(byte[] chars, int off, int len) {
+    public static boolean isNumber(byte[] buf, int off, int len) {
         for (int i = off, end = off + len; i < end; ++i) {
-            char ch = (char) chars[i];
+            char ch = (char) buf[i];
             if (ch == '+' || ch == '-') {
                 if (i != 0) {
                     return false;
@@ -751,24 +749,24 @@ public class IOUtils {
         return lines;
     }
 
-    public static int writeLocalDate(byte[] bytes, int off, int year, int month, int dayOfMonth) {
+    public static int writeLocalDate(byte[] buf, int off, int year, int month, int dayOfMonth) {
         if (year < 0) {
-            bytes[off++] = '-';
+            buf[off++] = '-';
             year = -year;
         } else if (year > 9999) {
-            bytes[off++] = '+';
+            buf[off++] = '+';
         }
         int y01 = (int) (year * 1374389535L >> 37); //year / 100;
         int y23 = year - y01 * 100;
 
         if (year >= 0 && year < 10000) {
-            writeDigitPair(bytes, off, y01);
+            writeDigitPair(buf, off, y01);
             off += 2;
         } else {
-            off = IOUtils.writeInt32(bytes, off, y01);
+            off = IOUtils.writeInt32(buf, off, y01);
         }
 
-        putLongLE(bytes, off,
+        putLongLE(buf, off,
                 0x2d00002d0000L
                         | digitPair(y23)
                         | ((long) digitPair(month) << 24)
@@ -776,34 +774,34 @@ public class IOUtils {
         return off + 8;
     }
 
-    public static int writeLocalDate(char[] chars, int off, int year, int month, int dayOfMonth) {
+    public static int writeLocalDate(char[] buf, int off, int year, int month, int dayOfMonth) {
         if (year < 0) {
-            chars[off++] = '-';
+            buf[off++] = '-';
             year = -year;
         } else if (year > 9999) {
-            chars[off++] = '+';
+            buf[off++] = '+';
         }
         int y01 = (int) (year * 1374389535L >> 37); // year / 100;
         int y23 = year - y01 * 100;
 
         if (year >= 0 && year < 10000) {
-            writeDigitPair(chars, off, y01);
+            writeDigitPair(buf, off, y01);
             off += 2;
         } else {
-            off = IOUtils.writeInt32(chars, off, y01);
+            off = IOUtils.writeInt32(buf, off, y01);
         }
 
         int p1 = PACKED_DIGITS_UTF16[month & 0x7f];
-        putLongLE(chars, off,
+        putLongLE(buf, off,
                 ((long) (p1 & 0xFFFF) << 48) | ((long) '-' << 32) | PACKED_DIGITS_UTF16[y23 & 0x7f]);
-        putLongLE(chars, off + 4,
+        putLongLE(buf, off + 4,
                 ((long) (p1 & 0xFFFF0000) >> 16) | ((long) '-' << 16) | ((long) PACKED_DIGITS_UTF16[dayOfMonth & 0x7f] << 32));
         return off + 8;
     }
 
-    public static void writeLocalTime(byte[] bytes, int off, int hour, int minute, int second) {
+    public static void writeLocalTime(byte[] buf, int off, int hour, int minute, int second) {
         putLongLE(
-                bytes,
+                buf,
                 off,
                 0x3a00003a0000L
                         | digitPair(hour)
@@ -811,19 +809,19 @@ public class IOUtils {
                         | ((long) digitPair(second) << 48));
     }
 
-    public static int writeLocalTime(byte[] bytes, int off, LocalTime time) {
-        writeLocalTime(bytes, off, time.getHour(), time.getMinute(), time.getSecond());
+    public static int writeLocalTime(byte[] buf, int off, LocalTime time) {
+        writeLocalTime(buf, off, time.getHour(), time.getMinute(), time.getSecond());
         off += 8;
         int nano = time.getNano();
-        return nano != 0 ? writeNano(bytes, off, nano) : off;
+        return nano != 0 ? writeNano(buf, off, nano) : off;
     }
 
-    public static int writeNano(byte[] bytes, int off, int nano) {
+    public static int writeNano(byte[] buf, int off, int nano) {
         final int div = (int) (nano * 274877907L >> 38); //nano / 1000;
         final int div2 = (int) (div * 274877907L >> 38); // div / 1000;
         final int rem1 = nano - div * 1000;
 
-        putIntLE(bytes, off, DIGITS_K_32[div2 & 0x3ff] & 0xffffff00 | '.');
+        putIntLE(buf, off, DIGITS_K_32[div2 & 0x3ff] & 0xffffff00 | '.');
         off += 4;
 
         int v;
@@ -838,23 +836,23 @@ public class IOUtils {
             v = DIGITS_K_32[(div - div2 * 1000) & 0x3ff];
         }
 
-        putShortLE(bytes, off, (short) (v >> 8));
+        putShortLE(buf, off, (short) (v >> 8));
         off += 2;
         if (rem1 == 0) {
-            putByte(bytes, off, (byte) (v >> 24));
+            putByte(buf, off, (byte) (v >> 24));
             return off + 1;
         }
 
-        putIntLE(bytes, off, DIGITS_K_32[rem1] & 0xffffff00 | (v >> 24));
+        putIntLE(buf, off, DIGITS_K_32[rem1] & 0xffffff00 | (v >> 24));
         return off + 4;
     }
 
-    public static int writeNano(char[] chars, int off, int nano) {
+    public static int writeNano(char[] buf, int off, int nano) {
         final int div = (int) (nano * 274877907L >> 38); //nano / 1000;
         final int div2 = (int) (div * 274877907L >> 38); // div / 1000;
         final int rem1 = nano - div * 1000;
 
-        putLongLE(chars, off, DIGITS_K_64[div2 & 0x3ff] & 0xffffffffffff0000L | DOT_X0);
+        putLongLE(buf, off, DIGITS_K_64[div2 & 0x3ff] & 0xffffffffffff0000L | DOT_X0);
         off += 4;
 
         long v;
@@ -869,31 +867,31 @@ public class IOUtils {
             v = DIGITS_K_64[(div - div2 * 1000) & 0x3ff];
         }
 
-        putIntLE(chars, off, (int) (v >> 16));
+        putIntLE(buf, off, (int) (v >> 16));
         off += 2;
         if (rem1 == 0) {
-            putChar(chars, off, (char) (v >> 48));
+            putChar(buf, off, (char) (v >> 48));
             return off + 1;
         }
 
-        putLongLE(chars, off, DIGITS_K_64[rem1 & 0x3ff] & 0xffffffffffff0000L | (v >> 48));
+        putLongLE(buf, off, DIGITS_K_64[rem1 & 0x3ff] & 0xffffffffffff0000L | (v >> 48));
         return off + 4;
     }
 
-    public static void writeLocalTime(char[] chars, int off, int hour, int minute, int second) {
-        writeDigitPair(chars, off, hour);
-        putChar(chars, off + 2, ':');
-        writeDigitPair(chars, off + 3, minute);
-        putChar(chars, off + 5, ':');
-        writeDigitPair(chars, off + 6, second);
+    public static void writeLocalTime(char[] buf, int off, int hour, int minute, int second) {
+        writeDigitPair(buf, off, hour);
+        putChar(buf, off + 2, ':');
+        writeDigitPair(buf, off + 3, minute);
+        putChar(buf, off + 5, ':');
+        writeDigitPair(buf, off + 6, second);
     }
 
-    public static int writeLocalTime(char[] chars, int off, LocalTime time) {
-        writeLocalTime(chars, off, time.getHour(), time.getMinute(), time.getSecond());
+    public static int writeLocalTime(char[] buf, int off, LocalTime time) {
+        writeLocalTime(buf, off, time.getHour(), time.getMinute(), time.getSecond());
         off += 8;
 
         int nano = time.getNano();
-        return nano != 0 ? writeNano(chars, off, nano) : off;
+        return nano != 0 ? writeNano(buf, off, nano) : off;
     }
 
     private static int writeInt4(byte[] buf, int off, int v) {
@@ -1245,20 +1243,20 @@ public class IOUtils {
         return writeInt8(buf, off, (int) (numValue - val * 10000), v1);
     }
 
-    public static byte getByte(byte[] str, int pos) {
-        return UNSAFE.getByte(str, ARRAY_CHAR_BASE_OFFSET + pos);
+    public static byte getByte(byte[] buf, int pos) {
+        return UNSAFE.getByte(buf, ARRAY_BYTE_BASE_OFFSET + pos);
     }
 
     public static char getChar(char[] buf, int pos) {
         return UNSAFE.getChar(buf, ARRAY_CHAR_BASE_OFFSET + ((long) pos << 1));
     }
 
-    public static char getChar(byte[] str, int pos) {
-        return UNSAFE.getChar(str, ARRAY_CHAR_BASE_OFFSET + ((long) pos << 1));
+    public static char getChar(byte[] buf, int pos) {
+        return UNSAFE.getChar(buf, ARRAY_BYTE_BASE_OFFSET + ((long) pos << 1));
     }
 
     private static void putByte(byte[] buf, int pos, byte v) {
-        UNSAFE.putByte(buf, ARRAY_CHAR_BASE_OFFSET + pos, v);
+        UNSAFE.putByte(buf, ARRAY_BYTE_BASE_OFFSET + pos, v);
     }
 
     private static void putChar(char[] buf, int pos, char v) {
@@ -1295,7 +1293,7 @@ public class IOUtils {
     }
 
     public static void putShortUnaligned(byte[] buf, int pos, short v) {
-        UNSAFE.putShort(buf, ARRAY_CHAR_BASE_OFFSET + pos, v);
+        UNSAFE.putShort(buf, ARRAY_BYTE_BASE_OFFSET + pos, v);
     }
 
     public static void putIntUnaligned(char[] buf, int pos, int v) {
@@ -1326,26 +1324,26 @@ public class IOUtils {
         UNSAFE.putLong(buf, ARRAY_BYTE_BASE_OFFSET + pos, convEndian(false, v));
     }
 
-    public static int putBoolean(byte[] bytes, int off, boolean v) {
+    public static int putBoolean(byte[] buf, int off, boolean v) {
         long address = ARRAY_BYTE_BASE_OFFSET + off;
         if (v) {
-            UNSAFE.putInt(bytes, address, TRUE);
+            UNSAFE.putInt(buf, address, TRUE);
             return off + 4;
         } else {
-            UNSAFE.putByte(bytes, address, (byte) 'f');
-            UNSAFE.putInt(bytes, address + 1, ALSE);
+            UNSAFE.putByte(buf, address, (byte) 'f');
+            UNSAFE.putInt(buf, address + 1, ALSE);
             return off + 5;
         }
     }
 
-    public static int putBoolean(char[] chars, int off, boolean v) {
+    public static int putBoolean(char[] buf, int off, boolean v) {
         long address = ARRAY_CHAR_BASE_OFFSET + ((long) off << 1);
         if (v) {
-            UNSAFE.putLong(chars, address, TRUE_64);
+            UNSAFE.putLong(buf, address, TRUE_64);
             return off + 4;
         } else {
-            UNSAFE.putChar(chars, address, 'f');
-            UNSAFE.putLong(chars, address + 2, ALSE_64);
+            UNSAFE.putChar(buf, address, 'f');
+            UNSAFE.putLong(buf, address + 2, ALSE_64);
             return off + 5;
         }
     }
@@ -1383,11 +1381,11 @@ public class IOUtils {
     }
 
     public static boolean isTRUE(char[] buf, int pos) {
-        return UNSAFE.getLong(buf, ARRAY_BYTE_BASE_OFFSET + ((long) pos << 1)) == TRUE_64;
+        return UNSAFE.getLong(buf, ARRAY_CHAR_BASE_OFFSET + ((long) pos << 1)) == TRUE_64;
     }
 
     public static boolean notTRUE(char[] buf, int pos) {
-        return UNSAFE.getLong(buf, ARRAY_BYTE_BASE_OFFSET + ((long) pos << 1)) != TRUE_64;
+        return UNSAFE.getLong(buf, ARRAY_CHAR_BASE_OFFSET + ((long) pos << 1)) != TRUE_64;
     }
 
     public static boolean isNULL(char[] buf, int pos) {
@@ -1399,15 +1397,15 @@ public class IOUtils {
     }
 
     public static void putNULL(byte[] buf, int pos) {
-        UNSAFE.putInt(buf, ARRAY_CHAR_BASE_OFFSET + pos, NULL_32);
+        UNSAFE.putInt(buf, ARRAY_BYTE_BASE_OFFSET + pos, NULL_32);
     }
 
     public static void putNULL(char[] buf, int pos) {
         UNSAFE.putLong(buf, ARRAY_CHAR_BASE_OFFSET + ((long) pos << 1), NULL_64);
     }
 
-    public static int digit4(char[] chars, int off) {
-        long x = getLongLE(chars, off);
+    public static int digit4(char[] buf, int off) {
+        long x = getLongLE(buf, off);
         long d;
         if ((((x & 0xFFF0FFF0FFF0FFF0L) - 0x30003000300030L) | (((d = x & 0x0F000F000F000FL) + 0x06000600060006L) & 0xF000F000F000F0L)) != 0) {
             return -1;
@@ -1419,9 +1417,9 @@ public class IOUtils {
                 (d >> 48));
     }
 
-    public static int digit4(byte[] bytes, int off) {
+    public static int digit4(byte[] buf, int off) {
         return digit4(
-                getIntLE(bytes, off)
+                getIntLE(buf, off)
         );
     }
 
@@ -1465,8 +1463,8 @@ public class IOUtils {
                 (d >> 24);
     }
 
-    public static int digit3(char[] chars, int off) {
-        long x = getIntLE(chars, off) + (((long) getChar(chars, off + 2)) << 32);
+    public static int digit3(char[] buf, int off) {
+        long x = getIntLE(buf, off) + (((long) getChar(buf, off + 2)) << 32);
         long d;
         if ((((x & 0xFFF0FFF0FFF0L) - 0x3000300030L) | (((d = x & 0x0F000F000FL) + 0x0600060006L) & 0xF000F000F0L)) != 0) {
             return -1;
@@ -1474,9 +1472,9 @@ public class IOUtils {
         return (int) (((d & 0xF) * 10 + ((d >> 16) & 0xF)) * 10 + (d >> 32));
     }
 
-    public static int digit3(byte[] bytes, int off) {
+    public static int digit3(byte[] buf, int off) {
         return digit3(
-                getShortLE(bytes, off) | (getByte(bytes, off + 2) << 16)
+                getShortLE(buf, off) | (getByte(buf, off + 2) << 16)
         );
     }
 
@@ -1488,8 +1486,8 @@ public class IOUtils {
         return ((d & 0xF) * 10 + ((d >> 8) & 0xF)) * 10 + (d >> 16);
     }
 
-    public static int digit2(char[] chars, int off) {
-        int x = UNSAFE.getInt(chars, ARRAY_CHAR_BASE_OFFSET + ((long) off << 1));
+    public static int digit2(char[] buf, int off) {
+        int x = UNSAFE.getInt(buf, ARRAY_CHAR_BASE_OFFSET + ((long) off << 1));
         if (BIG_ENDIAN) {
             x = Integer.reverseBytes(x);
         }
@@ -1500,8 +1498,8 @@ public class IOUtils {
         return (d & 0xF) * 10 + (d >> 16);
     }
 
-    public static int digit2(byte[] bytes, int off) {
-        short x = UNSAFE.getShort(bytes, ARRAY_BYTE_BASE_OFFSET + off);
+    public static int digit2(byte[] buf, int off) {
+        short x = UNSAFE.getShort(buf, ARRAY_BYTE_BASE_OFFSET + off);
         if (BIG_ENDIAN) {
             x = Short.reverseBytes(x);
         }
@@ -1512,16 +1510,16 @@ public class IOUtils {
         return (d & 0xF) * 10 + (d >> 8);
     }
 
-    public static boolean isDigit2(byte[] bytes, int off) {
-        short x = UNSAFE.getShort(bytes, ARRAY_BYTE_BASE_OFFSET + off);
+    public static boolean isDigit2(byte[] buf, int off) {
+        short x = UNSAFE.getShort(buf, ARRAY_BYTE_BASE_OFFSET + off);
         if (BIG_ENDIAN) {
             x = Short.reverseBytes(x);
         }
         return (((x & 0xF0F0) - 0x3030) | (((x & 0x0F0F) + 0x0606) & 0xF0F0)) == 0;
     }
 
-    public static boolean isDigit2(char[] bytes, int off) {
-        int x = UNSAFE.getShort(bytes, ARRAY_BYTE_BASE_OFFSET + ((long) off << 1));
+    public static boolean isDigit2(char[] buf, int off) {
+        int x = UNSAFE.getShort(buf, ARRAY_CHAR_BASE_OFFSET + ((long) off << 1));
         if (BIG_ENDIAN) {
             x = Integer.reverseBytes(x);
         }
@@ -1532,117 +1530,117 @@ public class IOUtils {
         return d >= 0 && d <= 9 ? d : -1;
     }
 
-    public static int digit1(char[] chars, int off) {
-        int d = UNSAFE.getByte(chars, ARRAY_CHAR_BASE_OFFSET + ((long) off << 1)) - '0';
+    public static int digit1(char[] buf, int off) {
+        int d = UNSAFE.getByte(buf, ARRAY_CHAR_BASE_OFFSET + ((long) off << 1)) - '0';
         return d >= 0 && d <= 9 ? d : -1;
     }
 
-    public static int digit1(byte[] bytes, int off) {
-        int d = UNSAFE.getByte(bytes, ARRAY_BYTE_BASE_OFFSET + off) - '0';
+    public static int digit1(byte[] buf, int off) {
+        int d = UNSAFE.getByte(buf, ARRAY_BYTE_BASE_OFFSET + off) - '0';
         return d >= 0 && d <= 9 ? d : -1;
     }
 
-    public static int indexOfQuote(byte[] value, int quote, int fromIndex, int max) {
+    public static int indexOfQuote(byte[] buf, int quote, int fromIndex, int max) {
         if (INDEX_OF_CHAR_LATIN1 == null) {
-            return indexOfQuoteV(value, quote, fromIndex, max);
+            return indexOfQuoteV(buf, quote, fromIndex, max);
         }
         try {
-            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(value, quote, fromIndex, max);
+            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(buf, quote, fromIndex, max);
         } catch (Throwable e) {
             throw new JSONException(e.getMessage());
         }
     }
 
-    public static int indexOfQuoteV(byte[] value, int quote, int fromIndex, int max) {
+    public static int indexOfQuoteV(byte[] buf, int quote, int fromIndex, int max) {
         int i = fromIndex;
         long address = ARRAY_BYTE_BASE_OFFSET + fromIndex;
         int upperBound = fromIndex + ((max - fromIndex) & ~7);
         long vectorQuote = quote == '\'' ? 0x2727_2727_2727_2727L : 0x2222_2222_2222_2222L;
-        while (i < upperBound && notContains(UNSAFE.getLong(value, address), vectorQuote)) {
+        while (i < upperBound && notContains(UNSAFE.getLong(buf, address), vectorQuote)) {
             i += 8;
             address += 8;
         }
-        return indexOfChar(value, quote, i, max);
+        return indexOfChar(buf, quote, i, max);
     }
 
-    public static int indexOfDoubleQuote(byte[] value, int fromIndex, int max) {
+    public static int indexOfDoubleQuote(byte[] buf, int fromIndex, int max) {
         if (INDEX_OF_CHAR_LATIN1 == null) {
-            return indexOfDoubleQuoteV(value, fromIndex, max);
+            return indexOfDoubleQuoteV(buf, fromIndex, max);
         }
         try {
-            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(value, (int) '"', fromIndex, max);
+            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(buf, (int) '"', fromIndex, max);
         } catch (Throwable e) {
             throw new JSONException(e.getMessage());
         }
     }
 
-    public static int indexOfDoubleQuoteV(byte[] value, int fromIndex, int max) {
+    public static int indexOfDoubleQuoteV(byte[] buf, int fromIndex, int max) {
         int i = fromIndex;
         long address = ARRAY_BYTE_BASE_OFFSET + fromIndex;
         int upperBound = fromIndex + ((max - fromIndex) & ~7);
-        while (i < upperBound && notContains(UNSAFE.getLong(value, address), 0x2222_2222_2222_2222L)) {
+        while (i < upperBound && notContains(UNSAFE.getLong(buf, address), 0x2222_2222_2222_2222L)) {
             i += 8;
             address += 8;
         }
-        return indexOfChar(value, '"', i, max);
+        return indexOfChar(buf, '"', i, max);
     }
 
-    public static int indexOfLineSeparator(byte[] value, int fromIndex, int max) {
+    public static int indexOfLineSeparator(byte[] buf, int fromIndex, int max) {
         if (INDEX_OF_CHAR_LATIN1 == null) {
-            return indexOfLineSeparatorV(value, fromIndex, max);
+            return indexOfLineSeparatorV(buf, fromIndex, max);
         }
         try {
-            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(value, (int) '\n', fromIndex, max);
+            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(buf, (int) '\n', fromIndex, max);
         } catch (Throwable e) {
             throw new JSONException(e.getMessage());
         }
     }
 
-    public static int indexOfLineSeparatorV(byte[] value, int fromIndex, int max) {
+    public static int indexOfLineSeparatorV(byte[] buf, int fromIndex, int max) {
         int i = fromIndex;
         long address = ARRAY_BYTE_BASE_OFFSET + fromIndex;
         int upperBound = fromIndex + ((max - fromIndex) & ~7);
-        while (i < upperBound && notContains(UNSAFE.getLong(value, address), 0x0A0A0A0A0A0A0A0AL)) {
+        while (i < upperBound && notContains(UNSAFE.getLong(buf, address), 0x0A0A0A0A0A0A0A0AL)) {
             i += 8;
             address += 8;
         }
-        return indexOfChar(value, '\n', i, max);
+        return indexOfChar(buf, '\n', i, max);
     }
 
-    public static int indexOfSlash(byte[] value, int fromIndex, int max) {
+    public static int indexOfSlash(byte[] buf, int fromIndex, int max) {
         if (INDEX_OF_CHAR_LATIN1 == null) {
-            return indexOfSlashV(value, fromIndex, max);
+            return indexOfSlashV(buf, fromIndex, max);
         }
         try {
-            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(value, (int) '\\', fromIndex, max);
+            return (int) INDEX_OF_CHAR_LATIN1.invokeExact(buf, (int) '\\', fromIndex, max);
         } catch (Throwable e) {
             throw new JSONException(e.getMessage());
         }
     }
 
-    public static int indexOfSlashV(byte[] value, int fromIndex, int max) {
+    public static int indexOfSlashV(byte[] buf, int fromIndex, int max) {
         int i = fromIndex;
         long address = ARRAY_BYTE_BASE_OFFSET + fromIndex;
         int upperBound = fromIndex + ((max - fromIndex) & ~7);
-        while (i < upperBound && notContains(UNSAFE.getLong(value, address), 0x5C5C5C5C5C5C5C5CL)) {
+        while (i < upperBound && notContains(UNSAFE.getLong(buf, address), 0x5C5C5C5C5C5C5C5CL)) {
             i += 8;
             address += 8;
         }
-        return indexOfChar(value, '\\', i, max);
+        return indexOfChar(buf, '\\', i, max);
     }
 
-    public static int indexOfChar(byte[] value, int ch, int fromIndex, int max) {
+    public static int indexOfChar(byte[] buf, int ch, int fromIndex, int max) {
         for (int i = fromIndex; i < max; i++) {
-            if (value[i] == ch) {
+            if (buf[i] == ch) {
                 return i;
             }
         }
         return -1;
     }
 
-    public static int indexOfChar(char[] value, int ch, int fromIndex, int max) {
+    public static int indexOfChar(char[] buf, int ch, int fromIndex, int max) {
         for (int i = fromIndex; i < max; i++) {
-            if (value[i] == ch) {
+            if (buf[i] == ch) {
                 return i;
             }
         }
@@ -1664,14 +1662,14 @@ public class IOUtils {
         return (((x - 0x0101010101010101L) & ~x) & 0x8080808080808080L) == 0;
     }
 
-    public static int hexDigit4(byte[] bytes, int offset) {
-        int v = getIntLE(bytes, offset);
+    public static int hexDigit4(byte[] buf, int offset) {
+        int v = getIntLE(buf, offset);
         v = (v & 0x0F0F0F0F) + ((((v & 0x40404040) >> 2) | ((v & 0x40404040) << 1)) >>> 4);
         return ((v & 0xF000000) >>> 24) + ((v & 0xF0000) >>> 12) + (v & 0xF00) + ((v & 0xF) << 12);
     }
 
-    public static int hexDigit4(char[] bytes, int offset) {
-        long v = getLongLE(bytes, offset);
+    public static int hexDigit4(char[] buf, int offset) {
+        long v = getLongLE(buf, offset);
         v = (v & 0x000F_000F_000F_000FL) + ((((v & 0x0004_0004_0004_00040L) >> 2) | ((v & 0x0004_0004_0004_00040L) << 1)) >>> 4);
         return (int) (((v & 0xF_0000_0000_0000L) >>> 48) + ((v & 0xF_0000_0000L) >>> 28) + ((v & 0xF_0000) >> 8) + ((v & 0xF) << 12));
     }
@@ -1680,80 +1678,80 @@ public class IOUtils {
         return ch >= '0' && ch <= '9';
     }
 
-    public static short getShortUnaligned(byte[] bytes, int offset) {
-        return UNSAFE.getShort(bytes, ARRAY_BYTE_BASE_OFFSET + offset);
+    public static short getShortUnaligned(byte[] buf, int offset) {
+        return UNSAFE.getShort(buf, ARRAY_BYTE_BASE_OFFSET + offset);
     }
 
-    public static short getShortBE(byte[] bytes, int offset) {
+    public static short getShortBE(byte[] buf, int offset) {
         return convEndian(true,
-                UNSAFE.getShort(bytes, ARRAY_BYTE_BASE_OFFSET + offset));
+                UNSAFE.getShort(buf, ARRAY_BYTE_BASE_OFFSET + offset));
     }
 
-    public static short getShortLE(byte[] bytes, int offset) {
+    public static short getShortLE(byte[] buf, int offset) {
         return convEndian(false,
-                UNSAFE.getShort(bytes, ARRAY_BYTE_BASE_OFFSET + offset));
+                UNSAFE.getShort(buf, ARRAY_BYTE_BASE_OFFSET + offset));
     }
 
-    public static boolean isUTF8BOM(byte[] bytes, int off) {
+    public static boolean isUTF8BOM(byte[] buf, int off) {
         // EF BB BF
-        return ((getIntLE(bytes, off)) & 0xFFFFFF) == 0xBFBBEF;
+        return ((getIntLE(buf, off)) & 0xFFFFFF) == 0xBFBBEF;
     }
 
-    public static int getIntBE(byte[] bytes, int offset) {
-        int v = UNSAFE.getInt(bytes, ARRAY_BYTE_BASE_OFFSET + offset);
+    public static int getIntBE(byte[] buf, int offset) {
+        int v = UNSAFE.getInt(buf, ARRAY_BYTE_BASE_OFFSET + offset);
         if (!BIG_ENDIAN) {
             v = Integer.reverseBytes(v);
         }
         return v;
     }
 
-    public static int getIntLE(byte[] bytes, int offset) {
-        int v = UNSAFE.getInt(bytes, ARRAY_BYTE_BASE_OFFSET + offset);
+    public static int getIntLE(byte[] buf, int offset) {
+        int v = UNSAFE.getInt(buf, ARRAY_BYTE_BASE_OFFSET + offset);
         if (BIG_ENDIAN) {
             v = Integer.reverseBytes(v);
         }
         return v;
     }
 
-    public static int getIntLE(char[] bytes, int offset) {
-        int v = UNSAFE.getInt(bytes, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
+    public static int getIntLE(char[] buf, int offset) {
+        int v = UNSAFE.getInt(buf, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
         if (BIG_ENDIAN) {
             v = Integer.reverseBytes(v);
         }
         return v;
     }
 
-    public static int getIntUnaligned(byte[] bytes, int offset) {
-        return UNSAFE.getInt(bytes, ARRAY_BYTE_BASE_OFFSET + offset);
+    public static int getIntUnaligned(byte[] buf, int offset) {
+        return UNSAFE.getInt(buf, ARRAY_BYTE_BASE_OFFSET + offset);
     }
 
-    public static int getIntUnaligned(char[] bytes, int offset) {
-        return UNSAFE.getInt(bytes, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
+    public static int getIntUnaligned(char[] buf, int offset) {
+        return UNSAFE.getInt(buf, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
     }
 
-    public static long getLongBE(byte[] bytes, int offset) {
-        long v = UNSAFE.getLong(bytes, ARRAY_BYTE_BASE_OFFSET + offset);
+    public static long getLongBE(byte[] buf, int offset) {
+        long v = UNSAFE.getLong(buf, ARRAY_BYTE_BASE_OFFSET + offset);
         if (!BIG_ENDIAN) {
             v = Long.reverseBytes(v);
         }
         return v;
     }
 
-    public static long getLongUnaligned(byte[] bytes, int offset) {
-        return UNSAFE.getLong(bytes, ARRAY_BYTE_BASE_OFFSET + offset);
+    public static long getLongUnaligned(byte[] buf, int offset) {
+        return UNSAFE.getLong(buf, ARRAY_BYTE_BASE_OFFSET + offset);
     }
 
-    public static long getLongUnaligned(char[] bytes, int offset) {
-        return UNSAFE.getLong(bytes, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
+    public static long getLongUnaligned(char[] buf, int offset) {
+        return UNSAFE.getLong(buf, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
     }
 
-    public static long getLongLE(byte[] bytes, int offset) {
+    public static long getLongLE(byte[] buf, int offset) {
         return convEndian(false,
-                UNSAFE.getLong(bytes, ARRAY_BYTE_BASE_OFFSET + offset));
+                UNSAFE.getLong(buf, ARRAY_BYTE_BASE_OFFSET + offset));
     }
 
-    public static long getLongLE(char[] bytes, int offset) {
-        long v = UNSAFE.getLong(bytes, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
+    public static long getLongLE(char[] buf, int offset) {
+        long v = UNSAFE.getLong(buf, ARRAY_CHAR_BASE_OFFSET + ((long) offset << 1));
         if (BIG_ENDIAN) {
             v = Long.reverseBytes(v);
         }
@@ -1854,18 +1852,18 @@ public class IOUtils {
         return big == BIG_ENDIAN ? n : Short.reverseBytes(n);
     }
 
-    public static boolean isLatin1(char[] chars, int off, int len) {
+    public static boolean isLatin1(char[] buf, int off, int len) {
         int end = off + len;
         int upperBound = off + (len & ~7);
         long address = ARRAY_CHAR_BASE_OFFSET + ((long) off << 1);
         while (off < upperBound
-                && (convEndian(false, UNSAFE.getLong(chars, address) | UNSAFE.getLong(chars, address + 8)) & 0xFF00FF00FF00FF00L) == 0
+                && (convEndian(false, UNSAFE.getLong(buf, address) | UNSAFE.getLong(buf, address + 8)) & 0xFF00FF00FF00FF00L) == 0
         ) {
             address += 16;
             off += 8;
         }
         while (off++ < end) {
-            if ((convEndian(false, UNSAFE.getShort(chars, address)) & 0xFF00) != 0) {
+            if ((convEndian(false, UNSAFE.getShort(buf, address)) & 0xFF00) != 0) {
                 return false;
             }
             address += 2;
@@ -1885,40 +1883,40 @@ public class IOUtils {
         return true;
     }
 
-    public static boolean isASCII(byte[] bytes) {
-        return isASCII(bytes, 0, bytes.length);
+    public static boolean isASCII(byte[] buf) {
+        return isASCII(buf, 0, buf.length);
     }
 
-    public static boolean isASCII(byte[] bytes, int off, int len) {
+    public static boolean isASCII(byte[] buf, int off, int len) {
         int end = off + len;
         int upperBound = off + (len & ~7);
         long address = ARRAY_BYTE_BASE_OFFSET + off;
-        while (off < upperBound && (UNSAFE.getLong(bytes, address) & 0x8080808080808080L) == 0) {
+        while (off < upperBound && (UNSAFE.getLong(buf, address) & 0x8080808080808080L) == 0) {
             address += 8;
             off += 8;
         }
 
         while (off++ < end) {
-            if ((UNSAFE.getByte(bytes, address++) & 0x80) != 0) {
+            if ((UNSAFE.getByte(buf, address++) & 0x80) != 0) {
                 return false;
             }
         }
         return true;
     }
 
-    public static boolean isNonSlashASCII(byte[] bytes, int off, int len) {
+    public static boolean isNonSlashASCII(byte[] buf, int off, int len) {
         int end = off + len;
         int upperBound = off + (len & ~7);
         long addr = ARRAY_BYTE_BASE_OFFSET + off;
         long d, x;
         while (off < upperBound
-                && (((d = UNSAFE.getLong(bytes, addr)) | (((x = d ^ 0x5C5C5C5C5C5C5C5CL) - 0x0101010101010101L) & ~x)) & 0x8080808080808080L) == 0) {
+                && (((d = UNSAFE.getLong(buf, addr)) | (((x = d ^ 0x5C5C5C5C5C5C5C5CL) - 0x0101010101010101L) & ~x)) & 0x8080808080808080L) == 0) {
             addr += 8;
             off += 8;
         }
         while (off++ < end) {
             byte b;
-            if (((b = UNSAFE.getByte(bytes, addr++)) & 0x80) != 0 || b == '\\') {
+            if (((b = UNSAFE.getByte(buf, addr++)) & 0x80) != 0 || b == '\\') {
                 return false;
             }
         }
@@ -1929,8 +1927,8 @@ public class IOUtils {
         return c >= '0' && c <= '9';
     }
 
-    public static int parseInt(byte[] bytes, int off, int len) {
-        int fc = bytes[off];
+    public static int parseInt(byte[] buf, int off, int len) {
+        int fc = buf[off];
         int result = isDigitLatin1(fc)
                 ? '0' - fc
                 : len != 1 && (fc == '-' || fc == '+')
@@ -1940,13 +1938,13 @@ public class IOUtils {
         off++;
         int d;
         while (off + 1 < end
-                && (d = IOUtils.digit2(bytes, off)) != -1
+                && (d = IOUtils.digit2(buf, off)) != -1
                 && Integer.MIN_VALUE / 100 <= result & result <= 0) {
             result = result * 100 - d;  // overflow from d => result > 0
             off += 2;
         }
         if (off < end
-                && isDigitLatin1(d = bytes[off])
+                && isDigitLatin1(d = buf[off])
                 && Integer.MIN_VALUE / 10 <= result & result <= 0) {
             result = result * 10 + '0' - d;  // overflow from '0' - d => result > 0
             off += 1;
@@ -1956,6 +1954,6 @@ public class IOUtils {
                 & (Integer.MIN_VALUE < result || fc == '-')) {
             return fc == '-' ? result : -result;
         }
-        throw new NumberFormatException(new String(bytes, off, len));
+        throw new NumberFormatException(new String(buf, off, len));
     }
 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/util/JDKUtils.java b/core/src/main/java/com/alibaba/fastjson2/util/JDKUtils.java
index 8d6f8db5a..33d470b38 100644
--- a/core/src/main/java/com/alibaba/fastjson2/util/JDKUtils.java
+++ b/core/src/main/java/com/alibaba/fastjson2/util/JDKUtils.java
@@ -70,22 +70,19 @@ public class JDKUtils {
 
     static {
         Unsafe unsafe;
-        long offset, charOffset;
         try {
             Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
             theUnsafeField.setAccessible(true);
             unsafe = (Unsafe) theUnsafeField.get(null);
-            offset = unsafe.arrayBaseOffset(byte[].class);
-            charOffset = unsafe.arrayBaseOffset(char[].class);
+            ARRAY_BYTE_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
+            ARRAY_CHAR_BASE_OFFSET = unsafe.arrayBaseOffset(char[].class);
         } catch (Throwable e) {
             throw new JSONException("init unsafe error", e);
         }
 
         UNSAFE = unsafe;
-        ARRAY_BYTE_BASE_OFFSET = offset;
-        ARRAY_CHAR_BASE_OFFSET = charOffset;
 
-        if (offset == -1) {
+        if (ARRAY_BYTE_BASE_OFFSET == -1 || ARRAY_CHAR_BASE_OFFSET == -1) {
             throw new JSONException("init JDKUtils error", initErrorLast);
         }
 
diff --git a/core/src/main/java/com/alibaba/fastjson2/util/TypeUtils.java b/core/src/main/java/com/alibaba/fastjson2/util/TypeUtils.java
index b6fadd5f9..3928632f3 100644
--- a/core/src/main/java/com/alibaba/fastjson2/util/TypeUtils.java
+++ b/core/src/main/java/com/alibaba/fastjson2/util/TypeUtils.java
@@ -366,7 +366,7 @@ public class TypeUtils {
                 break parseNumber; // go throw exception
             }
             if (isZero) {
-                return 0;
+                return isNegative ? -0D : 0D;
             }
             return doubleValue(isNegative, decExp, digits, nDigits);
         } catch (StringIndexOutOfBoundsException ignored) {
@@ -500,7 +500,7 @@ public class TypeUtils {
                 break parseNumber; // go throw exception
             }
             if (isZero) {
-                return 0;
+                return isNegative ? -0D : 0D;
             }
             return doubleValue(isNegative, decExp, digits, nDigits);
         } catch (StringIndexOutOfBoundsException ignored) {
@@ -634,7 +634,7 @@ public class TypeUtils {
                 break parseNumber; // go throw exception
             }
             if (isZero) {
-                return 0;
+                return isNegative ? -0F : 0F;
             }
             return floatValue(isNegative, decExp, digits, nDigits);
         } catch (StringIndexOutOfBoundsException ignored) {
@@ -767,7 +767,7 @@ public class TypeUtils {
                 break parseNumber; // go throw exception
             }
             if (isZero) {
-                return 0;
+                return isNegative ? -0F : 0F;
             }
             return floatValue(isNegative, decExp, digits, nDigits);
         } catch (StringIndexOutOfBoundsException ignored) {
diff --git a/docs/maven_deploy_guide.md b/docs/maven_deploy_guide.md
index b314e2ea9..589bfe3a8 100644
--- a/docs/maven_deploy_guide.md
+++ b/docs/maven_deploy_guide.md
@@ -4,12 +4,12 @@
 
 ## 0. 前置准备与配置
 
-在`Maven`的`settting.xml`中配置`oss.sonatype.org`账号：
+在`Maven`的`settting.xml`中配置`https://central.sonatype.com`账号：
 
 ```xml
 <servers>
     <server>
-        <id>ossrh</id>
+        <id>central</id>
         <username>__YOUR_USERNAME__</username>
         <password>__YOUR_PASSWORD__</password>
     </server>
@@ -18,15 +18,15 @@
 
 更多发布操作说明（如用于`GPG`签名的`GPG`安装与配置），参见：
 
-- OSSRH Guide  
-  https://central.sonatype.org/pages/ossrh-guide.html
-- Deploying to OSSRH with Apache Maven - Introduction  
-  https://central.sonatype.org/pages/apache-maven.html
+- Maven Central Portal Getting Started
+  https://central.sonatype.org/publish/publish-portal-guide/
+- Maven Central Portal Publishing By Using the Maven Plugin
+  https://central.sonatype.org/publish/publish-portal-maven/
 
 发布过程与发布文件的查看地址：
 
-- sonatype的发布控制台  
-  https://oss.sonatype.org/index.html
+- Maven Central Portal 的发布控制台  
+  https://central.sonatype.com
 - Maven中央库的文件查看  
   https://repo1.maven.org/maven2/com/alibaba/fastjson2/
 
diff --git a/pom.xml b/pom.xml
index a7cef5923..26b5118d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,12 +126,12 @@
 
     <distributionManagement>
         <repository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+            <id>central</id>
+            <url>https://central.sonatype.org/service/local/staging/deploy/maven2/</url>
         </repository>
         <snapshotRepository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+            <id>central</id>
+            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
         </snapshotRepository>
     </distributionManagement>
 
@@ -192,7 +192,7 @@
             <dependency>
                 <groupId>com.google.code.gson</groupId>
                 <artifactId>gson</artifactId>
-                <version>2.13.0</version>
+                <version>2.13.1</version>
             </dependency>
             <dependency>
                 <groupId>com.google.guava</groupId>
@@ -727,7 +727,7 @@
             <plugins>
                 <plugin>
                     <artifactId>maven-clean-plugin</artifactId>
-                    <version>3.4.1</version>
+                    <version>3.5.0</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-resources-plugin</artifactId>
@@ -1070,8 +1070,8 @@
                             </execution>
                         </executions>
                         <configuration>
-                            <serverId>ossrh</serverId>
-                            <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+                            <serverId>central</serverId>
+                            <nexusUrl>https://central.sonatype.org/</nexusUrl>
                             <autoReleaseAfterClose>true</autoReleaseAfterClose>
                             <!--
                             If you are deploying to Maven Central,


Test patch:
diff --git a/android-test/build.gradle b/android-test/build.gradle
index fa137f384..b82da5b0c 100644
--- a/android-test/build.gradle
+++ b/android-test/build.gradle
@@ -1,7 +1,7 @@
 buildscript {
     repositories {
         mavenLocal()
-        maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
+        maven { url "https://central.sonatype.com/repository/maven-snapshots/" }
         google()
         mavenCentral()
     }
@@ -15,7 +15,7 @@ buildscript {
 allprojects {
     repositories {
         mavenLocal()
-        maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
+        maven { url "https://central.sonatype.com/repository/maven-snapshots/" }
         google()
         mavenCentral()
     }
diff --git a/core/src/test/java/com/alibaba/fastjson2/JSONObjectTest.java b/core/src/test/java/com/alibaba/fastjson2/JSONObjectTest.java
index 07a808620..a05af69d7 100644
--- a/core/src/test/java/com/alibaba/fastjson2/JSONObjectTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/JSONObjectTest.java
@@ -8,6 +8,7 @@ import java.lang.reflect.Proxy;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.ZoneId;
@@ -1397,8 +1398,9 @@ public class JSONObjectTest {
         jsonObject.put("bytes", new byte[0]);
         assertEquals(0, jsonObject.getBytes("bytes").length);
 
-        jsonObject.put("bytes", Base64.getEncoder().encodeToString("abc中华人民共和国".getBytes()));
-        assertEquals("abc中华人民共和国", new String(jsonObject.getBytes("bytes")));
+        String str = "abc中华人民共和国";
+        jsonObject.put("bytes", Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)));
+        assertEquals(str, new String(jsonObject.getBytes("bytes"), StandardCharsets.UTF_8));
     }
 
     @Test
diff --git a/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java b/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java
index 284ef6f39..35690fb4d 100644
--- a/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java
@@ -1943,7 +1943,7 @@ public class JSONWriterTest {
                 ByteArrayOutputStream out = new ByteArrayOutputStream();
                 jsonWriter.flushTo(out);
                 byte[] outBytes = out.toByteArray();
-                assertEquals(v, new String(outBytes));
+                assertEquals(v, new String(outBytes, StandardCharsets.UTF_8));
             }
             {
                 JSONWriter jsonWriter = JSONWriter.ofUTF16();
@@ -1960,7 +1960,7 @@ public class JSONWriterTest {
                 ByteArrayOutputStream out = new ByteArrayOutputStream();
                 jsonWriter.flushTo(out);
                 byte[] outBytes = out.toByteArray();
-                assertEquals(v, new String(outBytes));
+                assertEquals(v, new String(outBytes, StandardCharsets.UTF_8));
             }
             {
                 JSONWriter jsonWriter = JSONWriter.ofUTF8();
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1543_1544.java b/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1543_1544.java
index 69bb24789..6019a08de 100644
--- a/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1543_1544.java
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1543_1544.java
@@ -30,7 +30,6 @@ public class Issue1543_1544 {
         try {
             JSON.toJSONString(list);
         } catch (Exception e) {
-            e.printStackTrace();
             assertTrue(e instanceof JSONException);
             assertEquals("level too large : 2049", e.getMessage());
         }
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2323.java b/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2323.java
index 2f04edf20..fa45e7c7f 100644
--- a/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2323.java
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2323.java
@@ -2,6 +2,7 @@ package com.alibaba.fastjson2.issues_2300;
 
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONLargeObjectException;
 import com.alibaba.fastjson2.JSONWriter;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -12,7 +13,7 @@ import java.util.List;
 import java.util.Map;
 
 public class Issue2323 {
-    String errMsg = "try enabling LargeObject feature instead";
+    String errMsgStart = "Maximum array size exceeded. Try enabling LargeObject feature instead. Requested size: ";
 
     @Test
     public void test() throws Exception {
@@ -32,20 +33,23 @@ public class Issue2323 {
 
         try {
             JSONWriter.ofUTF16().write(params);
-        } catch (OutOfMemoryError error) {
-            Assertions.assertEquals(errMsg, error.getMessage());
+        } catch (JSONLargeObjectException error) {
+            Assertions.assertTrue(error.getMessage().startsWith(errMsgStart),
+                    "Error message should start with: " + errMsgStart + ", but was: " + error.getMessage());
         }
 
         try {
             JSONWriter.ofUTF8().write(params);
-        } catch (OutOfMemoryError error) {
-            Assertions.assertEquals(errMsg, error.getMessage());
+        } catch (JSONLargeObjectException error) {
+            Assertions.assertTrue(error.getMessage().startsWith(errMsgStart),
+                    "Error message should start with: " + errMsgStart + ", but was: " + error.getMessage());
         }
 
         try {
             JSONWriter.ofJSONB().write(params);
-        } catch (OutOfMemoryError error) {
-            Assertions.assertEquals(errMsg, error.getMessage());
+        } catch (JSONLargeObjectException error) {
+            Assertions.assertTrue(error.getMessage().startsWith(errMsgStart),
+                    "Error message should start with: " + errMsgStart + ", but was: " + error.getMessage());
         }
     }
 }
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2375.java b/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2375.java
new file mode 100644
index 000000000..1c7110768
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_2300/Issue2375.java
@@ -0,0 +1,55 @@
+package com.alibaba.fastjson2.issues_2300;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.TypeReference;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.json.JSONException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author 张治保
+ * @since 2024/3/29
+ */
+public class Issue2375 {
+    @Test
+    void test() throws JSONException {
+        String jsonString = "{\"name\":\"name\",\"id\":\"1\",\"extra\":\"extra\"}";
+        Tree<String> tree = JSON.parseObject(jsonString, new TypeReference<Tree<String>>() {
+        });
+        JSONAssert.assertEquals(jsonString, JSON.toJSONString(tree), true);
+
+        Tree tree2 = JSON.parseObject(jsonString, Tree.class);
+        JSONAssert.assertEquals(jsonString, JSON.toJSONString(tree2), true);
+    }
+
+    @Test
+    void test2() throws JsonProcessingException {
+        String jsonString = "{\"treeList\":[{\"name\":\"name\",\"id\":\"1\",\"extra\":\"extra\"}]}";
+        DemoVO2 vo2 = JSON.parseObject(jsonString, DemoVO2.class);
+        List<Tree<String>> list = vo2.getTreeList();
+        DemoVO2 read = new ObjectMapper().readValue(jsonString, DemoVO2.class);
+        List<Tree<String>> list1 = read.getTreeList();
+        Assertions.assertEquals(list, list1);
+    }
+
+    private static class DemoVO2
+            implements Serializable {
+        private static final long serialVersionUID = 1L;
+        List<Tree<String>> treeList;
+
+        public List<Tree<String>> getTreeList() {
+            return treeList;
+        }
+
+        public void setTreeList(List<Tree<String>> treeList) {
+            this.treeList = treeList;
+        }
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3611.java b/core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3611.java
new file mode 100644
index 000000000..eb215fb84
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3611.java
@@ -0,0 +1,18 @@
+package com.alibaba.fastjson2.issues_3600;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3611 {
+    @Test
+    public void test() {
+        String str = "{\"二级指标\":\"一、主要经济指标\",\"核验扣分\":0,\"任务分配\":\"测试分解具体任务修改\",\"目标类型\":\"发展目标\",\"行政区划编码\":\"000000000000\",\"目标要求和评分标准\":\"A组其他6个设区市总量和增速分别占40%和60%的权重，B组7个设区市（xx、xx、xx、xx、xx、xx、xx）总量和增速分别占20%和80%的权重。计分公式：{[（某市变量-组内最小值）÷（组内最大值-组内最小值）]×0.4+0.6}×权重×分值\",\"完成标志\":\"未完成\",\"SZ_责任科室及联系人\":\"测试\",\"分解ID\":\"b649a98655a911f0fjw4e558814863f4\",\"SZ_是否分解给区县\":\"是\",\"分值\":5,\"得分值\":0}";
+        JSONObject jsonObject = JSON.parseObject(str);
+        String str1 = jsonObject.toJSONString();
+        JSONObject jsonObject1 = JSON.parseObject(str1);
+        assertEquals(jsonObject, jsonObject1);
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3613.java b/core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3613.java
new file mode 100644
index 000000000..0895b2a13
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3600/Issue3613.java
@@ -0,0 +1,74 @@
+package com.alibaba.fastjson2.issues_3600;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.util.TypeUtils;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3613 {
+    String str = "-0.0";
+    char[] chars = str.toCharArray();
+    byte[] utf8 = str.getBytes(StandardCharsets.UTF_8);
+    @Test
+    public void testFloat() {
+        assertEquals(
+                -0.0f,
+                JSON.parseObject(str, float.class));
+        assertEquals(
+                -0.0f,
+                JSON.parseObject(chars, float.class));
+        assertEquals(
+                -0.0f,
+                JSON.parseObject(utf8, float.class));
+
+        assertEquals(
+                -0.0f,
+                JSON.parseObject(str, Float.class));
+        assertEquals(
+                -0.0f,
+                JSON.parseObject(chars, Float.class));
+        assertEquals(
+                -0.0f,
+                JSON.parseObject(utf8, Float.class));
+
+        assertEquals(
+                -0.0F,
+                TypeUtils.parseFloat(chars, 0, chars.length));
+        assertEquals(
+                -0.0F,
+                TypeUtils.parseFloat(utf8, 0, utf8.length));
+    }
+
+    @Test
+    public void testDouble() {
+        assertEquals(
+                -0.0D,
+                JSON.parseObject(str, double.class));
+        assertEquals(
+                -0.0D,
+                JSON.parseObject(chars, double.class));
+        assertEquals(
+                -0.0D,
+                JSON.parseObject(utf8, double.class));
+
+        assertEquals(
+                -0.0D,
+                JSON.parseObject(str, Double.class));
+        assertEquals(
+                -0.0D,
+                JSON.parseObject(chars, Double.class));
+        assertEquals(
+                -0.0D,
+                JSON.parseObject(utf8, Double.class));
+
+        assertEquals(
+                -0.0D,
+                TypeUtils.parseDouble(chars, 0, chars.length));
+        assertEquals(
+                -0.0D,
+                TypeUtils.parseDouble(utf8, 0, utf8.length));
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/FastJson2Reader.java b/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/FastJson2Reader.java
new file mode 100644
index 000000000..6049ed341
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/FastJson2Reader.java
@@ -0,0 +1,30 @@
+package com.alibaba.fastjson2.issues_3600.issue3601;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.modules.ObjectReaderModule;
+import com.alibaba.fastjson2.reader.ObjectReader;
+import com.alibaba.fastjson2.reader.ObjectReaderImplList;
+import lombok.var;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+public class FastJson2Reader {
+    public static class JsonReaderModule
+            implements ObjectReaderModule {
+        @Override
+        public ObjectReader getObjectReader(Type type) {
+            if (type instanceof ParameterizedType) {
+                var ptype = (ParameterizedType) type;
+                if (ptype.getRawType() == MyArrayList.class) {
+                    return ObjectReaderImplList.of(type, null, 0);
+                }
+            }
+            return ObjectReaderModule.super.getObjectReader(type);
+        }
+    }
+
+    public static void init() {
+        JSON.register(new JsonReaderModule());
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/Issue3601.java b/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/Issue3601.java
new file mode 100644
index 000000000..200c81afa
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/Issue3601.java
@@ -0,0 +1,45 @@
+package com.alibaba.fastjson2.issues_3600.issue3601;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.var;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class Issue3601 {
+    @Test
+    public void test() {
+        FastJson2Reader.init();
+
+        var game = new Game();
+        game.players.addAll(Arrays.asList(new Player(1, 100), new Player(2, 200)));
+
+        var gameJson = JSON.toJSONString(game);
+        var deserializedGame1 = JSON.parseObject(gameJson, Game.class);
+        assertTrue(deserializedGame1.players.get(0).getClass() == Player.class);
+    }
+
+    public static class Player {
+        public int level;
+        public int exp;
+        public Player() {
+        }
+        public Player(int level, int exp) {
+            this.level = level;
+            this.exp = exp;
+        }
+    }
+
+    public static class Game {
+//        private ArrayList<Player> players = new ArrayList<>();
+        private MyArrayList<Player> players = new MyArrayList<>();
+        public MyArrayList<Player> getPlayers() {
+            return players;
+        }
+        public void setPlayers(MyArrayList<Player> players) {
+            this.players = players;
+        }
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/MyArrayList.java b/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/MyArrayList.java
new file mode 100644
index 000000000..238c1116a
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3600/issue3601/MyArrayList.java
@@ -0,0 +1,181 @@
+package com.alibaba.fastjson2.issues_3600.issue3601;
+
+import lombok.var;
+
+import java.util.*;
+
+public class MyArrayList<T>
+        implements List<T> {
+    private transient ArrayList<T> impl = new ArrayList<>();
+
+    public MyArrayList() {
+    }
+
+    public MyArrayList(Collection<T> coll) {
+        this.addAll(coll);
+    }
+
+    public MyArrayList(T... coll) {
+        for (T v : coll) {
+            this.add(v);
+        }
+    }
+
+    @Override
+    public int size() {
+        return impl.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return impl.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return impl.contains(o);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return impl.iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return impl.toArray();
+    }
+
+    @Override
+    public <V> V[] toArray(V[] a) {
+        return impl.toArray(a);
+    }
+
+    @Override
+    public boolean add(T v) {
+        boolean r = impl.add(v);
+        return r;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        int index = impl.indexOf(o);
+        var r = index >= 0;
+        return r;
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return impl.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends T> c) {
+        impl.ensureCapacity(impl.size() + c.size());
+        for (var v : c) {
+            this.add(v);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends T> c) {
+        impl.ensureCapacity(impl.size() + c.size());
+        for (var v : c) {
+            this.add(index++, v);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        for (var v : c) {
+            this.remove(v);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        throw new UnsupportedOperationException("retainAll");
+    }
+
+    @Override
+    public void clear() {
+        if (isEmpty()) {
+            return;
+        }
+        impl.clear();
+    }
+
+    @Override
+    public T get(int index) {
+        return impl.get(index);
+    }
+
+    @Override
+    public T set(int index, T element) {
+        var r = impl.set(index, element);
+        return r;
+    }
+
+    @Override
+    public void add(int index, T element) {
+        impl.add(index, element);
+    }
+
+    @Override
+    public T remove(int index) {
+        T prev = impl.remove(index);
+        return prev;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        return impl.indexOf(o);
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        return impl.lastIndexOf(o);
+    }
+
+    @Override
+    public ListIterator<T> listIterator() {
+        return impl.listIterator();
+    }
+
+    @Override
+    public ListIterator<T> listIterator(int index) {
+        return impl.listIterator(index);
+    }
+
+    @Override
+    public List<T> subList(int fromIndex, int toIndex) {
+        return Collections.unmodifiableList(impl.subList(fromIndex, toIndex));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        MyArrayList<?> dbList = (MyArrayList<?>) o;
+        return impl.equals(dbList.impl);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(impl);
+    }
+
+    @Override
+    public String toString() {
+        return "MyArrayList{" +
+                "impl=" + impl +
+                '}';
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/util/TypeUtilsTest.java b/core/src/test/java/com/alibaba/fastjson2/util/TypeUtilsTest.java
index e46f621ea..748341bb3 100644
--- a/core/src/test/java/com/alibaba/fastjson2/util/TypeUtilsTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/util/TypeUtilsTest.java
@@ -606,7 +606,7 @@ public class TypeUtilsTest {
             String s1 = Character.toString(i);
             String json = JSON.toJSONString(s1);
             assertEquals(s1, JSON.parse(json.toCharArray()));
-            assertEquals(s1, JSON.parse(json.getBytes()));
+            assertEquals(s1, JSON.parse(json.getBytes(StandardCharsets.UTF_8)));
         }
 
         for (char i = 0; i < 512; i++) {
@@ -614,7 +614,7 @@ public class TypeUtilsTest {
                 String s2 = new String(new char[]{i, j});
                 String json = JSON.toJSONString(s2);
                 assertEquals(s2, JSON.parse(json.toCharArray()));
-                assertEquals(s2, JSON.parse(json.getBytes()));
+                assertEquals(s2, JSON.parse(json.getBytes(StandardCharsets.UTF_8)));
             }
         }
     }
diff --git a/example-solon-test/pom.xml b/example-solon-test/pom.xml
index 58363d7d5..72a855c49 100644
--- a/example-solon-test/pom.xml
+++ b/example-solon-test/pom.xml
@@ -30,7 +30,7 @@
 
         <repository>
             <id>snapshots</id>
-            <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
         </repository>
     </repositories>
 
diff --git a/example-spring-test/pom.xml b/example-spring-test/pom.xml
index 49be7d2b9..b8e6e383d 100644
--- a/example-spring-test/pom.xml
+++ b/example-spring-test/pom.xml
@@ -38,7 +38,7 @@
         </repository>
         <repository>
             <id>snapshots</id>
-            <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
         </repository>
     </repositories>
 
diff --git a/test-jdk17/src/test/java/com/alibaba/fastjson2/issues/Issue3156.java b/test-jdk17/src/test/java/com/alibaba/fastjson2/issues/Issue3156.java
new file mode 100644
index 000000000..83ff08d38
--- /dev/null
+++ b/test-jdk17/src/test/java/com/alibaba/fastjson2/issues/Issue3156.java
@@ -0,0 +1,104 @@
+package com.alibaba.fastjson2.issues;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.TypeReference;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3156 {
+    @Test
+    public void test() {
+        String json = "{\"code\":200,\"data\":{\"userId\":1234556,\"name\":\"张三\"}}";
+
+        String json2 = "{\"code\":200,\"data\":{\"userId\":1234556,\"name\":\"张三\"},\"data2\":{\"userId\":1524231,\"name\":\"李四\"}}";
+
+        String json3 = "{\"code\":200,\"datas\":[{\"userId\":1,\"name\":\"a\"},{\"userId\":2,\"name\":\"b\"}]}";
+
+        String json4 = "{\"code\":200,\"data\":{\"info\":[{\"userId\":7,\"name\":\"x\"}]}}";
+
+        AjaxResult<LoginResult> r = JSON.parseObject(json, new TypeReference<AjaxResult<LoginResult>>() {
+        });
+        assertEquals(200, r.code());
+        assertEquals("LoginResult", r.data().getClass().getSimpleName());
+        assertEquals("张三", r.data().name);
+
+        AjaxResult2<LoginResult, LogoutResult> r2 = JSON.parseObject(json2, new TypeReference<AjaxResult2<LoginResult, LogoutResult>>() {
+        });
+        assertEquals("LoginResult", r2.data().getClass().getSimpleName());
+        assertEquals("LogoutResult", r2.data2().getClass().getSimpleName());
+
+        ListResult<LoginResult> r3 = JSON.parseObject(json3, new TypeReference<ListResult<LoginResult>>() {
+        });
+        assertEquals(2, r3.datas().size());
+        assertEquals("b", r3.datas().get(1).name);
+
+        MapListResult<String, LoginResult> r4 = JSON.parseObject(json4, new TypeReference<MapListResult<String, LoginResult>>() {
+        });
+        assertEquals("LoginResult", r4.data().get("info").get(0).getClass().getSimpleName());
+    }
+
+    @Data
+    public static class LoginResult {
+        private Long userId;
+        private String name;
+    }
+
+    @Data
+    public static class LogoutResult {
+        private Long userId;
+        private String name;
+    }
+
+    public record AjaxResult<T>(int code, String msg, T data) {
+    }
+
+    public record AjaxResult2<A, B>(int code, String msg, A data, B data2) {
+    }
+
+    public record ListResult<T>(int code, String msg, List<T> datas) {
+    }
+
+    public record MapListResult<K, V>(int code, String msg, Map<String, List<V>> data) {
+    }
+
+//    public static class AjaxResult<T> {
+//        private int code;
+//        private String msg;
+//        private T data;
+//
+//        public AjaxResult() {
+//        }
+//
+//        public int getCode() {
+//            return code;
+//        }
+//
+//        public AjaxResult<T> setCode(int code) {
+//            this.code = code;
+//            return this;
+//        }
+//
+//        public String getMsg() {
+//            return msg;
+//        }
+//
+//        public AjaxResult<T> setMsg(String msg) {
+//            this.msg = msg;
+//            return this;
+//        }
+//
+//        public T getData() {
+//            return data;
+//        }
+//
+//        public AjaxResult<T> setData(T data) {
+//            this.data = data;
+//            return this;
+//        }
+//    }
+}