This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new d0e92c5ca feat(json): support writing long values as strings (#4008)
d0e92c5ca is described below

commit d0e92c5caf9fe35fd786cd069e9cd6d0529c181b
Author: Shawn Yang <[email protected]>
AuthorDate: Mon Aug 31 14:16:29 2026 +0800

    feat(json): support writing long values as strings (#4008)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    Closes #3997
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence or equivalent persisted links of the
    final clean AI review results from both fresh reviewers described in
    `AI_POLICY.md`, the Fory-guided reviewer and the independent general
    reviewer, on the current PR diff or current HEAD after the latest code
    changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 docs/json/kotlin.md                                |   6 +
 docs/json/object-mapping.md                        |   9 +
 docs/json/scala.md                                 |   5 +
 .../java/org/apache/fory/json/ForyJsonBuilder.java |  17 ++
 .../main/java/org/apache/fory/json/JsonConfig.java |  10 +
 .../org/apache/fory/json/codec/ArrayCodec.java     | 152 +++++++++-
 .../apache/fory/json/codec/CollectionCodec.java    |  18 +-
 .../java/org/apache/fory/json/codec/MapCodec.java  |  18 +-
 .../org/apache/fory/json/codec/ScalarCodecs.java   | 150 +++++++++-
 .../fory/json/codegen/StringWriterCodegen.java     |  10 +-
 .../fory/json/codegen/Utf8WriterCodegen.java       |  20 +-
 .../org/apache/fory/json/meta/JsonFieldInfo.java   |  64 ++++-
 .../org/apache/fory/json/reader/JsonReader.java    |   7 +
 .../json/resolver/GeneratedCodecKeyBuilder.java    |   1 +
 .../fory/json/resolver/JsonSharedRegistry.java     |  40 ++-
 .../fory/json/resolver/JsonTypeResolver.java       |   6 +
 .../org/apache/fory/json/writer/JsonWriter.java    |   6 +
 .../apache/fory/json/writer/StringJsonWriter.java  | 253 +++++++++++++++++
 .../apache/fory/json/writer/Utf8JsonWriter.java    | 210 ++++++++++++++
 .../apache/fory/json/JsonAsyncCompilationTest.java |   1 +
 .../org/apache/fory/json/JsonContainerTest.java    |   5 +-
 .../org/apache/fory/json/JsonLongAsStringTest.java | 309 +++++++++++++++++++++
 .../java/org/apache/fory/json/JsonTestSupport.java |   1 +
 .../fory/json/kotlin/KotlinJsonCodecFactory.kt     |   6 +-
 .../fory/json/kotlin/KotlinUnsignedArrayCodecs.kt  |  37 ++-
 .../fory/json/kotlin/KotlinUnsignedCodecs.kt       |  92 +++++-
 .../json/kotlin/KotlinValueClassPrimitiveCodecs.kt |  60 +++-
 .../apache/fory/json/kotlin/ForyJsonKotlinTest.kt  | 122 ++++++++
 .../fory/json/kotlin/KotlinBuiltInCodecsTest.kt    |   2 +-
 .../apache/fory/json/scala/ScalaJsonSuite.scala    |  58 ++++
 30 files changed, 1643 insertions(+), 52 deletions(-)

diff --git a/docs/json/kotlin.md b/docs/json/kotlin.md
index 0a6825f3c..4b6bc1822 100644
--- a/docs/json/kotlin.md
+++ b/docs/json/kotlin.md
@@ -87,6 +87,12 @@ val text = json.toJson(Account(7u, "Alice"), accountType)
 val decoded = json.fromJson(text, accountType)
 ```
 
+Use `ForyJsonKotlin.builder().writeLongAsString(true)` when signed `Long` and 
unsigned `ULong`
+values must be emitted as quoted decimal strings. The setting also applies to 
their declared
+collection and map values, nullable values, Kotlin value classes backed by 
them, `ULongArray`, and
+the Java Long-like wrappers supported by the core JSON runtime. Readers accept 
both quoted and
+unquoted integer tokens.
+
 `jsonTypeRef<T>()` is a type token, not a codec lookup. Construct it once and 
reuse it. A Java
 `Class` or ordinary Java `TypeRef` cannot express distinctions such as 
`List<Account?>`, `UInt`, or
 a logical value class lowered to a primitive carrier.
diff --git a/docs/json/object-mapping.md b/docs/json/object-mapping.md
index e64b7116b..537c84860 100644
--- a/docs/json/object-mapping.md
+++ b/docs/json/object-mapping.md
@@ -215,6 +215,7 @@ original key type. Null map keys are rejected.
 | Builder method                         | Default                             
      | User-visible effect                                        |
 | -------------------------------------- | 
----------------------------------------- | 
---------------------------------------------------------- |
 | `writeNullFields(boolean)`             | `false`                             
      | Default inclusion of null object properties                |
+| `writeLongAsString(boolean)`           | `false`                             
      | Write built-in 64-bit integer values as decimal strings    |
 | `withCodegen(boolean)`                 | `true`                              
      | Enable generated object codecs                             |
 | `withAsyncCompilation(boolean)`        | `true`                              
      | Compile generated codecs asynchronously                    |
 | `withFieldMode(boolean)`               | `false`                             
      | When true, discover fields without getters/setters         |
@@ -225,6 +226,14 @@ original key type. Null map keys are rejected.
 | `registerCodec(type, codec)`           | None                                
      | Replace an eligible exact class's complete JSON codec      |
 | `registerMixin(mixinType)`             | None                                
      | Apply one annotation Mixin to its exact declared target    |
 
+Enable `writeLongAsString(true)` when 64-bit integer values must pass through 
JavaScript without
+`Number` precision loss. The setting writes built-in `long`/`Long`, 
`AtomicLong`,
+`AtomicLongArray`, and `OptionalLong` values as quoted decimal strings. It 
also follows declared
+Long children through arrays, collections, map values, `Optional<Long>`, 
`AtomicReference<Long>`,
+and equivalent language-module containers. Readers accept both numeric and 
quoted integer tokens
+regardless of this setting. Custom codecs and occurrence-level codec or format 
annotations retain
+their own output shape.
+
 Concurrency-level and buffer-retention limits must be positive. The 
cached-field-name limit
 applies independently to each reader; zero disables this cache. It bounds only 
cached field names,
 not names accepted from the input. The buffer-retention setting does not limit 
JSON input or output
diff --git a/docs/json/scala.md b/docs/json/scala.md
index 04b9637f3..f1bdf4a57 100644
--- a/docs/json/scala.md
+++ b/docs/json/scala.md
@@ -41,6 +41,11 @@ val person = json.fromJson(text, classOf[Person])
 ```
 
 Reuse the resulting `ForyJson` instance. It is immutable and thread-safe after 
construction.
+Use `ForyJsonScala.builder().writeLongAsString(true)` to emit Scala `Long` 
values, including
+declared collection and map values, `Option[Long]`, `Long`-backed value 
classes, and Java Long-like
+wrappers as quoted decimal strings. Readers accept both quoted and unquoted 
integer tokens.
+Use `ScalaTypeRef` when a parameterized declaration contains `Long` because 
normal JVM signatures
+can erase Scala value-type arguments to `Object`.
 
 ## Case classes and annotations
 
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java 
b/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
index 301d05b8c..805b64526 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
@@ -50,6 +50,7 @@ import org.apache.fory.platform.GraalvmSupport;
  */
 public final class ForyJsonBuilder {
   private boolean writeNullFields;
+  private boolean writeLongAsString;
   private boolean codegenEnabled = true;
   private boolean asyncCompilationEnabled = true;
   private boolean propertyDiscoveryEnabled = true;
@@ -80,6 +81,21 @@ public final class ForyJsonBuilder {
     return this;
   }
 
+  /**
+   * Writes signed 64-bit integer values owned by Fory's built-in JSON codecs 
as quoted decimal
+   * strings. This includes {@code long}/{@link Long}, {@code AtomicLong}, 
{@code AtomicLongArray},
+   * {@code OptionalLong}, and generic containers whose declared value is 
{@code Long}. Disabled by
+   * default.
+   *
+   * <p>This setting also applies to corresponding 64-bit unsigned scalar 
bindings installed by a
+   * language module. Exact custom codecs and occurrence-level codec or format 
annotations retain
+   * ownership of their complete representation.
+   */
+  public ForyJsonBuilder writeLongAsString(boolean writeLongAsString) {
+    this.writeLongAsString = writeLongAsString;
+    return this;
+  }
+
   /**
    * Enables generated object codecs for supported classes. Enabled by default 
and automatically
    * disabled on Android. A GraalVM Native Image includes generated codecs for 
the default
@@ -297,6 +313,7 @@ public final class ForyJsonBuilder {
         ModuleInstaller.install(new ArrayList<>(modules), codecRegistry, 
mixins);
     return new JsonConfig(
         writeNullFields,
+        writeLongAsString,
         effectiveCodegen,
         effectiveAsyncCompilation,
         propertyDiscoveryEnabled,
diff --git a/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java 
b/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
index a398f6b6f..029c81a58 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
@@ -37,6 +37,7 @@ public final class JsonConfig {
   private static final int MAX_CACHED_FIELD_NAMES = 1 << 29;
 
   private final boolean writeNullFields;
+  private final boolean writeLongAsString;
   private final boolean codegenEnabled;
   private final boolean asyncCompilationEnabled;
   private final boolean propertyDiscoveryEnabled;
@@ -56,6 +57,7 @@ public final class JsonConfig {
 
   JsonConfig(
       boolean writeNullFields,
+      boolean writeLongAsString,
       boolean codegenEnabled,
       boolean asyncCompilationEnabled,
       boolean propertyDiscoveryEnabled,
@@ -72,6 +74,7 @@ public final class JsonConfig {
       List<String> factoryIdentities,
       JsonTypeChecker typeChecker) {
     this.writeNullFields = writeNullFields;
+    this.writeLongAsString = writeLongAsString;
     this.codegenEnabled = codegenEnabled;
     this.asyncCompilationEnabled = asyncCompilationEnabled;
     this.propertyDiscoveryEnabled = propertyDiscoveryEnabled;
@@ -97,6 +100,13 @@ public final class JsonConfig {
     return writeNullFields;
   }
 
+  /**
+   * Returns whether built-in signed and module-provided unsigned 64-bit 
values write as strings.
+   */
+  public boolean writeLongAsString() {
+    return writeLongAsString;
+  }
+
   public boolean codegenEnabled() {
     return codegenEnabled;
   }
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
index cf3499eec..86c2a0244 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
@@ -80,6 +80,9 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
       return bind(IntArrayCodec.INSTANCE);
     } else if (componentType == long.class && componentCodec == 
ScalarCodecs.LongCodec.PRIMITIVE) {
       return bind(LongArrayCodec.INSTANCE);
+    } else if (componentType == long.class
+        && componentCodec == ScalarCodecs.LongAsStringCodec.PRIMITIVE) {
+      return bind(LongAsStringArrayCodec.INSTANCE);
     } else if (componentType == boolean.class
         && componentCodec == ScalarCodecs.BooleanCodec.PRIMITIVE) {
       return bind(BooleanArrayCodec.INSTANCE);
@@ -106,6 +109,12 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
           componentTypeInfo.rejectsNull()
               ? BoxedLongArrayCodec.NON_NULL
               : BoxedLongArrayCodec.INSTANCE);
+    } else if (componentType == Long.class
+        && componentCodec == ScalarCodecs.LongAsStringCodec.BOXED) {
+      return bind(
+          componentTypeInfo.rejectsNull()
+              ? BoxedLongAsStringArrayCodec.NON_NULL
+              : BoxedLongAsStringArrayCodec.INSTANCE);
     } else if (componentType == Boolean.class
         && componentCodec == ScalarCodecs.BooleanCodec.BOXED) {
       return bind(
@@ -152,7 +161,8 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
 
   /** Returns the exact unsigned primitive-array specialization for one 
semantic array id. */
   @Internal
-  public static <T> ArrayCodec<T> createUnsignedPrimitive(Class<T> arrayType, 
int typeId) {
+  public static <T> ArrayCodec<T> createUnsignedPrimitive(
+      Class<T> arrayType, int typeId, boolean writeLongAsString) {
     if (arrayType == byte[].class && typeId == Types.UINT8_ARRAY) {
       return bind(ByteArrayCodec.UNSIGNED);
     }
@@ -163,7 +173,10 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
       return bind(IntArrayCodec.UNSIGNED);
     }
     if (arrayType == long[].class && typeId == Types.UINT64_ARRAY) {
-      return bind(LongArrayCodec.UNSIGNED);
+      return bind(
+          writeLongAsString
+              ? UnsignedLongAsStringArrayCodec.INSTANCE
+              : UnsignedLongArrayCodec.INSTANCE);
     }
     throw new ForyJsonException(
         "Unsigned JSON array semantic id " + typeId + " does not match " + 
arrayType.getName());
@@ -389,11 +402,10 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
   // Keep the signed codec as the exact final loop owner. Routing signed long 
arrays through a
   // semantic element hook makes C2 inline the root and array loop into one 
unstable graph; the
   // unsigned codec below owns separate primitive operations so neither warmed 
path branches.
-  public static final class LongArrayCodec extends ArrayCodec<long[]> {
+  public static class LongArrayCodec extends ArrayCodec<long[]> {
     private static final LongArrayCodec INSTANCE = new LongArrayCodec();
-    private static final ArrayCodec<long[]> UNSIGNED = new 
UnsignedLongArrayCodec();
 
-    private LongArrayCodec() {
+    protected LongArrayCodec() {
       super(long.class);
     }
 
@@ -734,7 +746,44 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
     }
   }
 
-  private static final class UnsignedLongArrayCodec extends ArrayCodec<long[]> 
{
+  /** Exact signed long-array specialization which writes each element as a 
JSON string. */
+  public static final class LongAsStringArrayCodec extends LongArrayCodec {
+    private static final LongAsStringArrayCodec INSTANCE = new 
LongAsStringArrayCodec();
+
+    private LongAsStringArrayCodec() {}
+
+    @Override
+    public void writeString(StringJsonWriter writer, long[] value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      writer.writeArrayStart();
+      for (int i = 0; i < value.length; i++) {
+        writer.writeComma(i);
+        writer.writeLongAsString(value[i]);
+      }
+      writer.writeArrayEnd();
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, long[] value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      writer.writeArrayStart();
+      for (int i = 0; i < value.length; i++) {
+        writer.writeComma(i);
+        writer.writeLongAsString(value[i]);
+      }
+      writer.writeArrayEnd();
+    }
+  }
+
+  private static class UnsignedLongArrayCodec extends ArrayCodec<long[]> {
+    private static final UnsignedLongArrayCodec INSTANCE = new 
UnsignedLongArrayCodec();
+
     private UnsignedLongArrayCodec() {
       super(long.class);
     }
@@ -867,6 +916,41 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
     }
   }
 
+  private static final class UnsignedLongAsStringArrayCodec extends 
UnsignedLongArrayCodec {
+    private static final UnsignedLongAsStringArrayCodec INSTANCE =
+        new UnsignedLongAsStringArrayCodec();
+
+    private UnsignedLongAsStringArrayCodec() {}
+
+    @Override
+    public void writeString(StringJsonWriter writer, long[] value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      writer.writeArrayStart();
+      for (int i = 0; i < value.length; i++) {
+        writer.writeComma(i);
+        writer.writeUnsignedLongAsString(value[i]);
+      }
+      writer.writeArrayEnd();
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, long[] value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      writer.writeArrayStart();
+      for (int i = 0; i < value.length; i++) {
+        writer.writeComma(i);
+        writer.writeUnsignedLongAsString(value[i]);
+      }
+      writer.writeArrayEnd();
+    }
+  }
+
   public static final class BooleanArrayCodec extends ArrayCodec<boolean[]> {
     private static final BooleanArrayCodec INSTANCE = new BooleanArrayCodec();
     private static final int ELEMENT_BYTES = 1;
@@ -2662,12 +2746,12 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
     }
   }
 
-  public static final class BoxedLongArrayCodec extends ArrayCodec<Long[]> {
+  public static class BoxedLongArrayCodec extends ArrayCodec<Long[]> {
     private static final BoxedLongArrayCodec INSTANCE = new 
BoxedLongArrayCodec(false);
     private static final BoxedLongArrayCodec NON_NULL = new 
BoxedLongArrayCodec(true);
     private final boolean rejectsNull;
 
-    private BoxedLongArrayCodec(boolean rejectsNull) {
+    protected BoxedLongArrayCodec(boolean rejectsNull) {
       super(Long.class);
       this.rejectsNull = rejectsNull;
     }
@@ -2797,6 +2881,58 @@ public abstract class ArrayCodec<T> implements 
JsonValueCodec<T> {
     }
   }
 
+  private static final class BoxedLongAsStringArrayCodec extends 
BoxedLongArrayCodec {
+    private static final BoxedLongAsStringArrayCodec INSTANCE =
+        new BoxedLongAsStringArrayCodec(false);
+    private static final BoxedLongAsStringArrayCodec NON_NULL =
+        new BoxedLongAsStringArrayCodec(true);
+
+    private final boolean rejectsNull;
+
+    private BoxedLongAsStringArrayCodec(boolean rejectsNull) {
+      super(rejectsNull);
+      this.rejectsNull = rejectsNull;
+    }
+
+    @Override
+    public void writeString(StringJsonWriter writer, Long[] value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      writer.writeArrayStart();
+      for (int i = 0; i < value.length; i++) {
+        writer.writeComma(i);
+        Long element = value[i];
+        if (element == null) {
+          writeReferenceNull(writer, rejectsNull);
+        } else {
+          writer.writeLongAsString(element);
+        }
+      }
+      writer.writeArrayEnd();
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, Long[] value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      writer.writeArrayStart();
+      for (int i = 0; i < value.length; i++) {
+        writer.writeComma(i);
+        Long element = value[i];
+        if (element == null) {
+          writeReferenceNull(writer, rejectsNull);
+        } else {
+          writer.writeLongAsString(element);
+        }
+      }
+      writer.writeArrayEnd();
+    }
+  }
+
   public static final class BoxedBooleanArrayCodec extends 
ArrayCodec<Boolean[]> {
     private static final BoxedBooleanArrayCodec INSTANCE = new 
BoxedBooleanArrayCodec(false);
     private static final BoxedBooleanArrayCodec NON_NULL = new 
BoxedBooleanArrayCodec(true);
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
index 6c077b28c..840f31566 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
@@ -153,6 +153,9 @@ public abstract class CollectionCodec<T extends 
Collection<?>> implements JsonVa
     if (elementCodec == ScalarCodecs.LongCodec.BOXED) {
       return new LongCollectionCodec(factory, elementTypeInfo);
     }
+    if (elementCodec == ScalarCodecs.LongAsStringCodec.BOXED) {
+      return new LongAsStringCollectionCodec(factory, elementTypeInfo);
+    }
     if (elementCodec == ScalarCodecs.ShortCodec.BOXED) {
       return new ShortCollectionCodec(factory, elementTypeInfo);
     }
@@ -1942,8 +1945,8 @@ public abstract class CollectionCodec<T extends 
Collection<?>> implements JsonVa
     }
   }
 
-  public static final class LongCollectionCodec extends NumberCollectionCodec {
-    private LongCollectionCodec(CollectionFactory factory, JsonTypeInfo 
elementTypeInfo) {
+  public static class LongCollectionCodec extends NumberCollectionCodec {
+    protected LongCollectionCodec(CollectionFactory factory, JsonTypeInfo 
elementTypeInfo) {
       super(factory, elementTypeInfo);
     }
 
@@ -1968,6 +1971,17 @@ public abstract class CollectionCodec<T extends 
Collection<?>> implements JsonVa
     }
   }
 
+  private static final class LongAsStringCollectionCodec extends 
LongCollectionCodec {
+    private LongAsStringCollectionCodec(CollectionFactory factory, 
JsonTypeInfo elementTypeInfo) {
+      super(factory, elementTypeInfo);
+    }
+
+    @Override
+    void writeNumber(JsonWriter writer, Object value) {
+      writer.writeLongAsString((long) value);
+    }
+  }
+
   public static final class ShortCollectionCodec extends NumberCollectionCodec 
{
     private ShortCollectionCodec(CollectionFactory factory, JsonTypeInfo 
elementTypeInfo) {
       super(factory, elementTypeInfo);
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
index 355f374f9..bd2231aed 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
@@ -189,6 +189,9 @@ public abstract class MapCodec<T extends Map<?, ?>> 
implements JsonValueCodec<T>
       if (valueCodec == ScalarCodecs.LongCodec.BOXED) {
         return new StringLongMapCodec(factory, valueTypeInfo);
       }
+      if (valueCodec == ScalarCodecs.LongAsStringCodec.BOXED) {
+        return new StringLongAsStringMapCodec(factory, valueTypeInfo);
+      }
       if (valueCodec == ScalarCodecs.ShortCodec.BOXED) {
         return new StringShortMapCodec(factory, valueTypeInfo);
       }
@@ -1037,8 +1040,8 @@ public abstract class MapCodec<T extends Map<?, ?>> 
implements JsonValueCodec<T>
     }
   }
 
-  public static final class StringLongMapCodec extends StringNumberMapCodec {
-    private StringLongMapCodec(MapFactory factory, JsonTypeInfo valueTypeInfo) 
{
+  public static class StringLongMapCodec extends StringNumberMapCodec {
+    protected StringLongMapCodec(MapFactory factory, JsonTypeInfo 
valueTypeInfo) {
       super(factory, valueTypeInfo);
     }
 
@@ -1063,6 +1066,17 @@ public abstract class MapCodec<T extends Map<?, ?>> 
implements JsonValueCodec<T>
     }
   }
 
+  private static final class StringLongAsStringMapCodec extends 
StringLongMapCodec {
+    private StringLongAsStringMapCodec(MapFactory factory, JsonTypeInfo 
valueTypeInfo) {
+      super(factory, valueTypeInfo);
+    }
+
+    @Override
+    void writeNumber(JsonWriter writer, Object value) {
+      writer.writeLongAsString((long) value);
+    }
+  }
+
   public static final class StringShortMapCodec extends StringNumberMapCodec {
     private StringShortMapCodec(MapFactory factory, JsonTypeInfo 
valueTypeInfo) {
       super(factory, valueTypeInfo);
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
index 2e33d4ecd..71ea52dae 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
@@ -412,12 +412,12 @@ public final class ScalarCodecs {
     }
   }
 
-  public static final class LongCodec implements JsonValueCodec<Long> {
+  public static class LongCodec implements JsonValueCodec<Long> {
     public static final LongCodec PRIMITIVE = new LongCodec(true);
     public static final LongCodec BOXED = new LongCodec(false);
     private final boolean primitive;
 
-    private LongCodec(boolean primitive) {
+    protected LongCodec(boolean primitive) {
       this.primitive = primitive;
     }
 
@@ -464,6 +464,34 @@ public final class ScalarCodecs {
     }
   }
 
+  /** Built-in signed Long binding which writes decimal digits as a JSON 
string. */
+  public static final class LongAsStringCodec extends LongCodec {
+    public static final LongAsStringCodec PRIMITIVE = new 
LongAsStringCodec(true);
+    public static final LongAsStringCodec BOXED = new LongAsStringCodec(false);
+
+    private LongAsStringCodec(boolean primitive) {
+      super(primitive);
+    }
+
+    @Override
+    public void writeString(StringJsonWriter writer, Long value) {
+      if (value == null) {
+        writer.writeNull();
+      } else {
+        writer.writeLongAsString(value);
+      }
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, Long value) {
+      if (value == null) {
+        writer.writeNull();
+      } else {
+        writer.writeLongAsString(value);
+      }
+    }
+  }
+
   public static final class ShortCodec implements JsonValueCodec<Short> {
     public static final ShortCodec PRIMITIVE = new ShortCodec(true);
     public static final ShortCodec BOXED = new ShortCodec(false);
@@ -2631,9 +2659,11 @@ public final class ScalarCodecs {
     }
   }
 
-  public static final class AtomicLongCodec implements 
JsonValueCodec<AtomicLong> {
+  public static class AtomicLongCodec implements JsonValueCodec<AtomicLong> {
     public static final AtomicLongCodec INSTANCE = new AtomicLongCodec();
 
+    protected AtomicLongCodec() {}
+
     @Override
     public void writeString(StringJsonWriter writer, AtomicLong value) {
       if (value == null) {
@@ -2668,6 +2698,31 @@ public final class ScalarCodecs {
     }
   }
 
+  /** Built-in AtomicLong binding which writes its Long value as a JSON 
string. */
+  public static final class AtomicLongAsStringCodec extends AtomicLongCodec {
+    public static final AtomicLongAsStringCodec INSTANCE = new 
AtomicLongAsStringCodec();
+
+    private AtomicLongAsStringCodec() {}
+
+    @Override
+    public void writeString(StringJsonWriter writer, AtomicLong value) {
+      if (value == null) {
+        writer.writeNull();
+      } else {
+        writer.writeLongAsString(value.get());
+      }
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, AtomicLong value) {
+      if (value == null) {
+        writer.writeNull();
+      } else {
+        writer.writeLongAsString(value.get());
+      }
+    }
+  }
+
   public static class AtomicReferenceCodec implements 
JsonValueCodec<AtomicReference<?>> {
     static final int SHALLOW_BYTES = 
GraphMemoryEstimates.shallowObjectBytes(AtomicReference.class);
 
@@ -2970,9 +3025,11 @@ public final class ScalarCodecs {
     }
   }
 
-  public static final class AtomicLongArrayCodec implements 
JsonValueCodec<AtomicLongArray> {
+  public static class AtomicLongArrayCodec implements 
JsonValueCodec<AtomicLongArray> {
     public static final AtomicLongArrayCodec INSTANCE = new 
AtomicLongArrayCodec();
 
+    protected AtomicLongArrayCodec() {}
+
     @Override
     public void writeString(StringJsonWriter writer, AtomicLongArray value) {
       if (value == null) {
@@ -3088,6 +3145,43 @@ public final class ScalarCodecs {
     }
   }
 
+  /** Built-in AtomicLongArray binding which writes each Long value as a JSON 
string. */
+  public static final class AtomicLongArrayAsStringCodec extends 
AtomicLongArrayCodec {
+    public static final AtomicLongArrayAsStringCodec INSTANCE = new 
AtomicLongArrayAsStringCodec();
+
+    private AtomicLongArrayAsStringCodec() {}
+
+    @Override
+    public void writeString(StringJsonWriter writer, AtomicLongArray value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      AtomicLongArray array = value;
+      writer.writeArrayStart();
+      for (int i = 0, length = array.length(); i < length; i++) {
+        writer.writeComma(i);
+        writer.writeLongAsString(array.get(i));
+      }
+      writer.writeArrayEnd();
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, AtomicLongArray value) {
+      if (value == null) {
+        writer.writeNull();
+        return;
+      }
+      AtomicLongArray array = value;
+      writer.writeArrayStart();
+      for (int i = 0, length = array.length(); i < length; i++) {
+        writer.writeComma(i);
+        writer.writeLongAsString(array.get(i));
+      }
+      writer.writeArrayEnd();
+    }
+  }
+
   public static class AtomicReferenceArrayCodec implements 
JsonValueCodec<AtomicReferenceArray<?>> {
     private static final int SHALLOW_BYTES =
         GraphMemoryEstimates.shallowObjectBytes(AtomicReferenceArray.class);
@@ -3486,14 +3580,14 @@ public final class ScalarCodecs {
     }
   }
 
-  public static final class OptionalLongCodec
+  public static class OptionalLongCodec
       implements JsonValueCodec<OptionalLong>, TransparentNullCodec {
     public static final OptionalLongCodec INSTANCE = new 
OptionalLongCodec(false);
     @Internal public static final OptionalLongCodec NON_NULL = new 
OptionalLongCodec(true);
 
-    private final boolean requireOwner;
+    protected final boolean requireOwner;
 
-    private OptionalLongCodec(boolean requireOwner) {
+    protected OptionalLongCodec(boolean requireOwner) {
       this.requireOwner = requireOwner;
     }
 
@@ -3543,6 +3637,48 @@ public final class ScalarCodecs {
     }
   }
 
+  /** Built-in OptionalLong binding which writes its present Long value as a 
JSON string. */
+  public static final class OptionalLongAsStringCodec extends 
OptionalLongCodec {
+    public static final OptionalLongAsStringCodec INSTANCE = new 
OptionalLongAsStringCodec(false);
+
+    @Internal
+    public static final OptionalLongAsStringCodec NON_NULL = new 
OptionalLongAsStringCodec(true);
+
+    private OptionalLongAsStringCodec(boolean requireOwner) {
+      super(requireOwner);
+    }
+
+    @Override
+    public void writeString(StringJsonWriter writer, OptionalLong value) {
+      if (value == null) {
+        requireOptionalOwner(requireOwner);
+        writer.writeNull();
+        return;
+      }
+      OptionalLong optional = value;
+      if (optional.isPresent()) {
+        writer.writeLongAsString(optional.getAsLong());
+      } else {
+        writer.writeNull();
+      }
+    }
+
+    @Override
+    public void writeUtf8(Utf8JsonWriter writer, OptionalLong value) {
+      if (value == null) {
+        requireOptionalOwner(requireOwner);
+        writer.writeNull();
+        return;
+      }
+      OptionalLong optional = value;
+      if (optional.isPresent()) {
+        writer.writeLongAsString(optional.getAsLong());
+      } else {
+        writer.writeNull();
+      }
+    }
+  }
+
   public static final class OptionalDoubleCodec
       implements JsonValueCodec<OptionalDouble>, TransparentNullCodec {
     public static final OptionalDoubleCodec INSTANCE = new 
OptionalDoubleCodec(false);
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/StringWriterCodegen.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/StringWriterCodegen.java
index b898fc847..3bb77ba1e 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/StringWriterCodegen.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/StringWriterCodegen.java
@@ -183,7 +183,10 @@ final class StringWriterCodegen extends JsonWriterCodegen {
         method = "writeObjectStartWithIntField";
         break;
       case LONG:
-        method = "writeObjectStartWithLongField";
+        method =
+            property.writesLongAsString()
+                ? "writeObjectStartWithLongAsStringField"
+                : "writeObjectStartWithLongField";
         break;
       default:
         throw new ForyJsonException(
@@ -212,7 +215,10 @@ final class StringWriterCodegen extends JsonWriterCodegen {
       boolean commaKnown,
       Expression index,
       Expression writer) {
-    String method = longValue ? "writeLongField" : "writeIntField";
+    String method =
+        longValue
+            ? property.writesLongAsString() ? "writeLongAsStringField" : 
"writeLongField"
+            : "writeIntField";
     if (commaKnown) {
       if (canPackUtf16Prefix(property, true)) {
         return new Expression.Invoke(
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/Utf8WriterCodegen.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/Utf8WriterCodegen.java
index 7b0b8ba1a..1c49ba284 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/Utf8WriterCodegen.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/Utf8WriterCodegen.java
@@ -187,7 +187,10 @@ final class Utf8WriterCodegen extends JsonWriterCodegen {
         method = "writeObjectStartWithIntField";
         break;
       case LONG:
-        method = "writeObjectStartWithLongField";
+        method =
+            property.writesLongAsString()
+                ? "writeObjectStartWithLongAsStringField"
+                : "writeObjectStartWithLongField";
         break;
       default:
         throw new ForyJsonException(
@@ -226,13 +229,21 @@ final class Utf8WriterCodegen extends JsonWriterCodegen {
       boolean commaKnown,
       Expression index,
       Expression writer) {
-    String method = longValue ? "writeLongField" : "writeIntField";
+    String method =
+        longValue
+            ? property.writesLongAsString() ? "writeLongAsStringField" : 
"writeLongField"
+            : "writeIntField";
     if (commaKnown) {
       if (canPackPrefix(property, true)) {
         if (inlineSchemaWrites) {
           return new Expression.ListExpression(
               directPackedPrefix(property, id),
-              new Expression.Invoke(writer, longValue ? "writeLong" : 
"writeInt", value));
+              new Expression.Invoke(
+                  writer,
+                  longValue
+                      ? property.writesLongAsString() ? "writeLongAsString" : 
"writeLong"
+                      : "writeInt",
+                  value));
         }
         return new Expression.Invoke(writer, method, 
packedPrefixArgs(property, true, value));
       }
@@ -368,6 +379,9 @@ final class Utf8WriterCodegen extends JsonWriterCodegen {
     if (rawType == long[].class && codecType == 
ArrayCodec.LongArrayCodec.class) {
       return new Expression.Invoke(writer, "writeLongArray", value);
     }
+    if (rawType == long[].class && codecType == 
ArrayCodec.LongAsStringArrayCodec.class) {
+      return new Expression.Invoke(writer, "writeLongArrayAsString", value);
+    }
     return null;
   }
 
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java 
b/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
index 7ecbb8127..5dac22025 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
@@ -32,6 +32,7 @@ import org.apache.fory.json.annotation.JsonFormat;
 import org.apache.fory.json.codec.CodecUtils;
 import org.apache.fory.json.codec.DirectUnboxedValueCodec;
 import org.apache.fory.json.codec.JsonValueCodec;
+import org.apache.fory.json.codec.ScalarCodecs;
 import org.apache.fory.json.codec.TransparentUnboxedValueCodec;
 import org.apache.fory.json.codec.UnboxedValueCodec;
 import org.apache.fory.json.reader.JsonReader;
@@ -81,6 +82,7 @@ public final class JsonFieldInfo {
   private static final int KIND_RAW_STRING = 16;
   private static final int KIND_NULL = 17;
   private static final int KIND_UNBOXED = 18;
+  private static final int KIND_LONG_AS_STRING = 19;
   private static final int WRITE_NULL_MASK = Integer.MIN_VALUE;
   private static final int REQUIRE_NON_NULL_MASK = 1 << 30;
   private static final int READ_INDEX_MASK = REQUIRE_NON_NULL_MASK - 1;
@@ -463,6 +465,11 @@ public final class JsonFieldInfo {
     return writeKindId == KIND_RAW_STRING;
   }
 
+  /** Returns whether the resolved built-in Long binding writes quoted decimal 
digits. */
+  public boolean writesLongAsString() {
+    return writeKind == JsonFieldKind.LONG && isLongAsString(writeTypeInfo);
+  }
+
   public JsonFieldAccessor writeAccessor() {
     return writeAccessor;
   }
@@ -579,7 +586,13 @@ public final class JsonFieldInfo {
       }
       if (!rawString && writeRawType != void.class) {
         writeKind = writeTypeInfo.kind();
-        writeKindId = writeUnboxedValueCodec == null ? kindId(writeKind) : 
KIND_UNBOXED;
+        if (writeUnboxedValueCodec != null) {
+          writeKindId = KIND_UNBOXED;
+        } else if (writeKind == JsonFieldKind.LONG && 
isLongAsString(writeTypeInfo)) {
+          writeKindId = KIND_LONG_AS_STRING;
+        } else {
+          writeKindId = kindId(writeKind);
+        }
       }
       if (writeUnboxedValueCodec != null
           && !writeOccurrenceTypeInfo.nullable()
@@ -610,6 +623,12 @@ public final class JsonFieldInfo {
     }
   }
 
+  private static boolean isLongAsString(JsonTypeInfo typeInfo) {
+    Object codec = typeInfo.stringWriter();
+    return codec == ScalarCodecs.LongAsStringCodec.PRIMITIVE
+        || codec == ScalarCodecs.LongAsStringCodec.BOXED;
+  }
+
   private UnboxedValueCodec requireUnboxed(
       JsonTypeInfo canonical, Class<?> carrier, String direction) {
     UnboxedValueCodec operation = canonical.unboxedValueCodec();
@@ -1108,6 +1127,13 @@ public final class JsonFieldInfo {
         writer.writeLongField(
             stringNamePrefix, stringCommaNamePrefix, index, 
writeAccessor.getLong(object));
         return true;
+      case KIND_LONG_AS_STRING:
+        if (!writeRawType.isPrimitive()) {
+          return writeStringLongAsString(writer, object, index);
+        }
+        writer.writeLongAsStringField(
+            stringNamePrefix, stringCommaNamePrefix, index, 
writeAccessor.getLong(object));
+        return true;
       case KIND_FLOAT:
         if (!writeRawType.isPrimitive()) {
           return writeStringScalar(writer, object, index);
@@ -1190,6 +1216,13 @@ public final class JsonFieldInfo {
         writer.writeLongField(
             utf8NamePrefix, utf8CommaNamePrefix, index, 
writeAccessor.getLong(object));
         return true;
+      case KIND_LONG_AS_STRING:
+        if (!writeRawType.isPrimitive()) {
+          return writeUtf8LongAsString(writer, object, index);
+        }
+        writer.writeLongAsStringField(
+            utf8NamePrefix, utf8CommaNamePrefix, index, 
writeAccessor.getLong(object));
+        return true;
       case KIND_FLOAT:
         if (!writeRawType.isPrimitive()) {
           return writeUtf8Scalar(writer, object, index);
@@ -1292,6 +1325,21 @@ public final class JsonFieldInfo {
     }
   }
 
+  private boolean writeStringLongAsString(StringJsonWriter writer, Object 
object, int index) {
+    Long value = (Long) writeAccessor.getObject(object);
+    if (value == null && !writeNull()) {
+      return omitNullValue();
+    }
+    if (value == null) {
+      writer.writeFieldName(this, index);
+      writer.writeNull();
+    } else {
+      writer.writeLongAsStringField(
+          stringNamePrefix, stringCommaNamePrefix, index, value.longValue());
+    }
+    return true;
+  }
+
   private boolean writeUtf8Scalar(Utf8JsonWriter writer, Object object, int 
index) {
     Object value = writeAccessor.getObject(object);
     if (value == null && !writeNull()) {
@@ -1328,6 +1376,20 @@ public final class JsonFieldInfo {
     }
   }
 
+  private boolean writeUtf8LongAsString(Utf8JsonWriter writer, Object object, 
int index) {
+    Long value = (Long) writeAccessor.getObject(object);
+    if (value == null && !writeNull()) {
+      return omitNullValue();
+    }
+    if (value == null) {
+      writer.writeFieldName(this, index);
+      writer.writeNull();
+    } else {
+      writer.writeLongAsStringField(utf8NamePrefix, utf8CommaNamePrefix, 
index, value.longValue());
+    }
+    return true;
+  }
+
   private boolean writeStringText(StringJsonWriter writer, Object object, int 
index) {
     String value = (String) writeAccessor.getObject(object);
     if (value == null && !writeNull()) {
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/reader/JsonReader.java 
b/java/fory-json/src/main/java/org/apache/fory/json/reader/JsonReader.java
index dfd4359f0..1438f0143 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/reader/JsonReader.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/reader/JsonReader.java
@@ -892,6 +892,13 @@ public abstract class JsonReader {
   /** Reads one canonical unsigned 64-bit JSON integer and returns its raw 
bits. */
   public final long readUnsignedLong() {
     skipWhitespace();
+    if (position < length() && charAt(position) == '"') {
+      beginQuotedScalar();
+      long value = readUnsignedDigits();
+      rejectFractionOrExponent();
+      finishQuotedScalar();
+      return value;
+    }
     long value = readUnsignedDigits();
     rejectFractionOrExponent();
     return value;
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/GeneratedCodecKeyBuilder.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/GeneratedCodecKeyBuilder.java
index b1853db49..d6f5f9f52 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/GeneratedCodecKeyBuilder.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/GeneratedCodecKeyBuilder.java
@@ -72,6 +72,7 @@ final class GeneratedCodecKeyBuilder {
     JsonSharedRegistry registry = resolver.sharedRegistry();
     if (!JsonTypeResolver.readerKind(kind)) {
       keyParts.add(registry.writeNullFields());
+      keyParts.add(registry.writeLongAsString());
     }
     keyParts.add(registry.propertyDiscoveryEnabled());
     keyParts.add(registry.propertyNamingStrategy());
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
index 4c8b53ce4..60fe63ddc 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
@@ -187,6 +187,7 @@ public final class JsonSharedRegistry {
   private final boolean propertyDiscoveryEnabled;
   private final PropertyNamingStrategy propertyNamingStrategy;
   private final boolean writeNullFields;
+  private final boolean writeLongAsString;
   private final ClassLoader classLoader;
   private final JsonMixinAnnotations mixinAnnotations;
   private final IdentityHashMap<Class<?>, JsonSubTypesInfo> subTypesCache;
@@ -243,6 +244,7 @@ public final class JsonSharedRegistry {
     this.propertyDiscoveryEnabled = config.propertyDiscoveryEnabled();
     propertyNamingStrategy = config.propertyNamingStrategy();
     writeNullFields = config.writeNullFields();
+    writeLongAsString = config.writeLongAsString();
     classLoader = config.classLoader();
     mixinAnnotations = new JsonMixinAnnotations(config);
     exactCodecs = new IdentityHashMap<>();
@@ -957,7 +959,11 @@ public final class JsonSharedRegistry {
         return new ResolvedCodec(ScalarCodecs.OptionalIntCodec.NON_NULL, null);
       }
       if (rawType == OptionalLong.class) {
-        return new ResolvedCodec(ScalarCodecs.OptionalLongCodec.NON_NULL, 
null);
+        return new ResolvedCodec(
+            writeLongAsString
+                ? ScalarCodecs.OptionalLongAsStringCodec.NON_NULL
+                : ScalarCodecs.OptionalLongCodec.NON_NULL,
+            null);
       }
       return new ResolvedCodec(ScalarCodecs.OptionalDoubleCodec.NON_NULL, 
null);
     }
@@ -1215,6 +1221,10 @@ public final class JsonSharedRegistry {
     return writeNullFields;
   }
 
+  boolean writeLongAsString() {
+    return writeLongAsString;
+  }
+
   ClassLoader classLoader() {
     return classLoader;
   }
@@ -2146,8 +2156,14 @@ public final class JsonSharedRegistry {
     exactCodecs.put(Boolean.class, ScalarCodecs.BooleanCodec.BOXED);
     exactCodecs.put(int.class, ScalarCodecs.IntCodec.PRIMITIVE);
     exactCodecs.put(Integer.class, ScalarCodecs.IntCodec.BOXED);
-    exactCodecs.put(long.class, ScalarCodecs.LongCodec.PRIMITIVE);
-    exactCodecs.put(Long.class, ScalarCodecs.LongCodec.BOXED);
+    exactCodecs.put(
+        long.class,
+        writeLongAsString
+            ? ScalarCodecs.LongAsStringCodec.PRIMITIVE
+            : ScalarCodecs.LongCodec.PRIMITIVE);
+    exactCodecs.put(
+        Long.class,
+        writeLongAsString ? ScalarCodecs.LongAsStringCodec.BOXED : 
ScalarCodecs.LongCodec.BOXED);
     exactCodecs.put(short.class, ScalarCodecs.ShortCodec.PRIMITIVE);
     exactCodecs.put(Short.class, ScalarCodecs.ShortCodec.BOXED);
     exactCodecs.put(byte.class, ScalarCodecs.ByteCodec.PRIMITIVE);
@@ -2168,8 +2184,16 @@ public final class JsonSharedRegistry {
     exactCodecs.put(AtomicBoolean.class, 
ScalarCodecs.AtomicBooleanCodec.INSTANCE);
     exactCodecs.put(AtomicInteger.class, 
ScalarCodecs.AtomicIntegerCodec.INSTANCE);
     exactCodecs.put(AtomicIntegerArray.class, 
ScalarCodecs.AtomicIntegerArrayCodec.INSTANCE);
-    exactCodecs.put(AtomicLong.class, ScalarCodecs.AtomicLongCodec.INSTANCE);
-    exactCodecs.put(AtomicLongArray.class, 
ScalarCodecs.AtomicLongArrayCodec.INSTANCE);
+    exactCodecs.put(
+        AtomicLong.class,
+        writeLongAsString
+            ? ScalarCodecs.AtomicLongAsStringCodec.INSTANCE
+            : ScalarCodecs.AtomicLongCodec.INSTANCE);
+    exactCodecs.put(
+        AtomicLongArray.class,
+        writeLongAsString
+            ? ScalarCodecs.AtomicLongArrayAsStringCodec.INSTANCE
+            : ScalarCodecs.AtomicLongArrayCodec.INSTANCE);
     exactCodecs.put(Currency.class, ScalarCodecs.CurrencyCodec.INSTANCE);
     exactCodecs.put(File.class, ScalarCodecs.FileCodec.INSTANCE);
     exactCodecs.put(URI.class, ScalarCodecs.UriCodec.INSTANCE);
@@ -2201,7 +2225,11 @@ public final class JsonSharedRegistry {
     exactCodecs.put(MinguoDate.class, ScalarCodecs.MinguoDateCodec.INSTANCE);
     exactCodecs.put(ThaiBuddhistDate.class, 
ScalarCodecs.ThaiBuddhistDateCodec.INSTANCE);
     exactCodecs.put(OptionalInt.class, ScalarCodecs.OptionalIntCodec.INSTANCE);
-    exactCodecs.put(OptionalLong.class, 
ScalarCodecs.OptionalLongCodec.INSTANCE);
+    exactCodecs.put(
+        OptionalLong.class,
+        writeLongAsString
+            ? ScalarCodecs.OptionalLongAsStringCodec.INSTANCE
+            : ScalarCodecs.OptionalLongCodec.INSTANCE);
     exactCodecs.put(OptionalDouble.class, 
ScalarCodecs.OptionalDoubleCodec.INSTANCE);
     exactCodecs.put(ByteBuffer.class, ScalarCodecs.ByteBufferCodec.INSTANCE);
     GuavaCodecs.registerExactCodecs(exactCodecs);
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
index 14357eb25..5ee441691 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
@@ -138,6 +138,12 @@ public final class JsonTypeResolver {
     return sharedRegistry;
   }
 
+  /** Returns whether this runtime writes default 64-bit integral values as 
JSON strings. */
+  @Internal
+  public boolean writeLongAsString() {
+    return sharedRegistry.writeLongAsString();
+  }
+
   @Internal
   public void lockJIT() {
     jitContext.lock();
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/writer/JsonWriter.java 
b/java/fory-json/src/main/java/org/apache/fory/json/writer/JsonWriter.java
index 15c88f9ea..4b72e2b37 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/writer/JsonWriter.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/writer/JsonWriter.java
@@ -109,6 +109,9 @@ public abstract class JsonWriter {
 
   public abstract void writeLong(long value);
 
+  /** Writes a signed 64-bit value as a quoted decimal JSON string. */
+  public abstract void writeLongAsString(long value);
+
   /** Writes raw unsigned 32-bit bits as a decimal JSON number. */
   public void writeUnsignedInt(int value) {
     writeLong(Integer.toUnsignedLong(value));
@@ -117,6 +120,9 @@ public abstract class JsonWriter {
   /** Writes raw unsigned 64-bit bits as a decimal JSON number. */
   public abstract void writeUnsignedLong(long value);
 
+  /** Writes raw unsigned 64-bit bits as a quoted decimal JSON string. */
+  public abstract void writeUnsignedLongAsString(long value);
+
   public abstract void writeFloat(float value);
 
   public abstract void writeDouble(double value);
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
index 9c10f102e..eb17bc32e 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
@@ -203,6 +203,25 @@ public final class StringJsonWriter extends JsonWriter 
implements Appendable {
     writeLongUtf16(value);
   }
 
+  @Override
+  public void writeLongAsString(long value) {
+    if (coder == LATIN1) {
+      if (position + 22 > buffer.length) {
+        grow(22);
+      }
+      buffer[position++] = (byte) '"';
+      writeLongLatin1NoEnsure(value);
+      buffer[position++] = (byte) '"';
+      return;
+    }
+    if (position + 44 > buffer.length) {
+      grow(44);
+    }
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
   @Override
   public void writeUnsignedLong(long value) {
     if (value >= 0) {
@@ -215,6 +234,25 @@ public final class StringJsonWriter extends JsonWriter 
implements Appendable {
     writeByteRaw((byte) ('0' + remainder));
   }
 
+  @Override
+  public void writeUnsignedLongAsString(long value) {
+    if (coder == LATIN1) {
+      if (position + 22 > buffer.length) {
+        grow(22);
+      }
+      buffer[position++] = (byte) '"';
+      writeUnsignedLongLatin1NoEnsure(value);
+      buffer[position++] = (byte) '"';
+      return;
+    }
+    if (position + 44 > buffer.length) {
+      grow(44);
+    }
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeUnsignedLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
   private void writeLongLatin1(long value) {
     if (value == Long.MIN_VALUE) {
       writeRaw(MIN_LONG_BYTES);
@@ -1122,6 +1160,199 @@ public final class StringJsonWriter extends JsonWriter 
implements Appendable {
     writeLongUtf16NoEnsure(value);
   }
 
+  public void writeLongAsStringField(
+      byte[] namePrefix, byte[] commaNamePrefix, int index, long value) {
+    writeLongAsStringField(index == 0 ? namePrefix : commaNamePrefix, value);
+  }
+
+  public void writeLongAsStringField(
+      byte[] namePrefix,
+      byte[] commaNamePrefix,
+      byte[] utf16NamePrefix,
+      byte[] utf16CommaNamePrefix,
+      int index,
+      long value) {
+    if (coder == LATIN1) {
+      writeLongAsStringField(index == 0 ? namePrefix : commaNamePrefix, value);
+      return;
+    }
+    writeLongAsStringFieldUtf16Value(index == 0 ? utf16NamePrefix : 
utf16CommaNamePrefix, value);
+  }
+
+  public void writeLongAsStringField(byte[] prefix, long value) {
+    if (coder == LATIN1) {
+      writeLongAsStringFieldLatin1(prefix, value);
+      return;
+    }
+    writeLongAsStringFieldUtf16(prefix, value);
+  }
+
+  public void writeLongAsStringField(byte[] prefix, byte[] utf16Prefix, long 
value) {
+    if (coder == LATIN1) {
+      writeLongAsStringFieldLatin1(prefix, value);
+      return;
+    }
+    writeLongAsStringFieldUtf16Value(utf16Prefix, value);
+  }
+
+  public void writeLongAsStringField(
+      byte[] prefix,
+      long utf16Prefix0,
+      long utf16Prefix1,
+      long utf16Prefix2,
+      long utf16Prefix3,
+      int utf16PrefixLength,
+      long value) {
+    if (coder == LATIN1) {
+      writeLongAsStringFieldLatin1(prefix, value);
+      return;
+    }
+    writeLongAsStringFieldUtf16Packed(
+        utf16Prefix0, utf16Prefix1, utf16Prefix2, utf16Prefix3, 
utf16PrefixLength, value);
+  }
+
+  private void writeLongAsStringFieldLatin1(byte[] prefix, long value) {
+    int additional = prefix.length + 22;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeRawLatin1NoEnsure(prefix);
+    buffer[position++] = (byte) '"';
+    writeLongLatin1NoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
+  private void writeLongAsStringFieldUtf16(byte[] prefix, long value) {
+    int additional = (prefix.length << 1) + 44;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeRawUtf16NoEnsure(prefix);
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
+  private void writeLongAsStringFieldUtf16Value(byte[] utf16Prefix, long 
value) {
+    int additional = utf16Prefix.length + 44;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeRawUtf16ValueNoEnsure(utf16Prefix);
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
+  private void writeLongAsStringFieldUtf16Packed(
+      long utf16Prefix0,
+      long utf16Prefix1,
+      long utf16Prefix2,
+      long utf16Prefix3,
+      int utf16PrefixLength,
+      long value) {
+    int additional = Math.max(packedUtf16PrefixSize(utf16PrefixLength), 
utf16PrefixLength + 44);
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writePackedUtf16ValueNoEnsure(
+        utf16Prefix0, utf16Prefix1, utf16Prefix2, utf16Prefix3, 
utf16PrefixLength);
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
+  public void writeObjectStartWithLongAsStringField(byte[] namePrefix, long 
value) {
+    enterDepth();
+    if (coder == LATIN1) {
+      writeObjectStartWithLongAsStringFieldLatin1(namePrefix, value);
+      return;
+    }
+    writeObjectStartWithLongAsStringFieldUtf16(namePrefix, value);
+  }
+
+  public void writeObjectStartWithLongAsStringField(
+      byte[] namePrefix, byte[] utf16NamePrefix, long value) {
+    enterDepth();
+    if (coder == LATIN1) {
+      writeObjectStartWithLongAsStringFieldLatin1(namePrefix, value);
+      return;
+    }
+    writeObjectStartWithLongAsStringFieldUtf16Value(utf16NamePrefix, value);
+  }
+
+  public void writeObjectStartWithLongAsStringField(
+      byte[] namePrefix,
+      long utf16Prefix0,
+      long utf16Prefix1,
+      long utf16Prefix2,
+      long utf16Prefix3,
+      int utf16PrefixLength,
+      long value) {
+    enterDepth();
+    if (coder == LATIN1) {
+      writeObjectStartWithLongAsStringFieldLatin1(namePrefix, value);
+      return;
+    }
+    writeObjectStartWithLongAsStringFieldUtf16Packed(
+        utf16Prefix0, utf16Prefix1, utf16Prefix2, utf16Prefix3, 
utf16PrefixLength, value);
+  }
+
+  private void writeObjectStartWithLongAsStringFieldLatin1(byte[] namePrefix, 
long value) {
+    int additional = namePrefix.length + 23;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    buffer[position++] = (byte) '{';
+    writeRawLatin1NoEnsure(namePrefix);
+    buffer[position++] = (byte) '"';
+    writeLongLatin1NoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
+  private void writeObjectStartWithLongAsStringFieldUtf16(byte[] namePrefix, 
long value) {
+    int additional = ((namePrefix.length + 1) << 1) + 44;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeUtf16ByteNoEnsure((byte) '{');
+    writeRawUtf16NoEnsure(namePrefix);
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
+  private void writeObjectStartWithLongAsStringFieldUtf16Value(byte[] 
utf16NamePrefix, long value) {
+    int additional = utf16NamePrefix.length + 46;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeUtf16ByteNoEnsure((byte) '{');
+    writeRawUtf16ValueNoEnsure(utf16NamePrefix);
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
+  private void writeObjectStartWithLongAsStringFieldUtf16Packed(
+      long utf16Prefix0,
+      long utf16Prefix1,
+      long utf16Prefix2,
+      long utf16Prefix3,
+      int utf16PrefixLength,
+      long value) {
+    int additional = Math.max(packedUtf16PrefixSize(utf16PrefixLength), 
utf16PrefixLength + 46);
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeUtf16ByteNoEnsure((byte) '{');
+    writePackedUtf16ValueNoEnsure(
+        utf16Prefix0, utf16Prefix1, utf16Prefix2, utf16Prefix3, 
utf16PrefixLength);
+    writeUtf16ByteNoEnsure((byte) '"');
+    writeLongUtf16NoEnsure(value);
+    writeUtf16ByteNoEnsure((byte) '"');
+  }
+
   public void writeStringField(byte[] namePrefix, byte[] commaNamePrefix, int 
index, String value) {
     writeRaw(index == 0 ? namePrefix : commaNamePrefix);
     writeString(value);
@@ -2507,6 +2738,17 @@ public final class StringJsonWriter extends JsonWriter 
implements Appendable {
     writePositiveLongLatin1NoEnsure(value);
   }
 
+  private void writeUnsignedLongLatin1NoEnsure(long value) {
+    if (value >= 0) {
+      writeLongLatin1NoEnsure(value);
+      return;
+    }
+    long quotient = Long.divideUnsigned(value, 10);
+    int remainder = (int) Long.remainderUnsigned(value, 10);
+    writeLongLatin1NoEnsure(quotient);
+    buffer[position++] = (byte) ('0' + remainder);
+  }
+
   private void writePositiveLongLatin1NoEnsure(long value) {
     if (value <= Integer.MAX_VALUE) {
       writePositiveIntNoEnsure((int) value);
@@ -2563,6 +2805,17 @@ public final class StringJsonWriter extends JsonWriter 
implements Appendable {
     position = writePositiveLongUtf16(bytes, pos, value);
   }
 
+  private void writeUnsignedLongUtf16NoEnsure(long value) {
+    if (value >= 0) {
+      writeLongUtf16NoEnsure(value);
+      return;
+    }
+    long quotient = Long.divideUnsigned(value, 10);
+    int remainder = (int) Long.remainderUnsigned(value, 10);
+    writeLongUtf16NoEnsure(quotient);
+    writeUtf16ByteNoEnsure((byte) ('0' + remainder));
+  }
+
   private void writePositiveIntNoEnsure(int value) {
     byte[] bytes = buffer;
     int pos = position;
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java 
b/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
index 7a612e5a2..652ef3439 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
@@ -188,6 +188,16 @@ public final class Utf8JsonWriter extends JsonWriter 
implements Appendable {
     writeLongNoEnsure(value);
   }
 
+  @Override
+  public void writeLongAsString(long value) {
+    if (position + 22 > buffer.length) {
+      grow(22);
+    }
+    buffer[position++] = (byte) '"';
+    writeLongNoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
   @Override
   public void writeUnsignedLong(long value) {
     if (value >= 0) {
@@ -200,6 +210,23 @@ public final class Utf8JsonWriter extends JsonWriter 
implements Appendable {
     buffer[position++] = (byte) ('0' + remainder);
   }
 
+  @Override
+  public void writeUnsignedLongAsString(long value) {
+    if (position + 22 > buffer.length) {
+      grow(22);
+    }
+    buffer[position++] = (byte) '"';
+    if (value >= 0) {
+      writeLongNoEnsure(value);
+    } else {
+      long quotient = Long.divideUnsigned(value, 10);
+      int remainder = (int) Long.remainderUnsigned(value, 10);
+      writeLongNoEnsure(quotient);
+      buffer[position++] = (byte) ('0' + remainder);
+    }
+    buffer[position++] = (byte) '"';
+  }
+
   @Override
   public void writeFloat(float value) {
     if (!Float.isFinite(value)) {
@@ -978,6 +1005,76 @@ public final class Utf8JsonWriter extends JsonWriter 
implements Appendable {
     writeLongFieldNoEnsure(value);
   }
 
+  public void writeLongAsStringField(
+      byte[] namePrefix, byte[] commaNamePrefix, int index, long value) {
+    writeLongAsStringField(index == 0 ? namePrefix : commaNamePrefix, value);
+  }
+
+  public void writeLongAsStringField(byte[] prefix, long value) {
+    int additional = prefix.length + 22;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writeRawNoEnsure(prefix);
+    buffer[position++] = (byte) '"';
+    writeLongFieldNoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
+  public void writeLongAsStringField(long prefix0, long prefix1, int 
prefixLength, long value) {
+    int additional = Math.max(packedPrefixSize(prefixLength), prefixLength + 
22);
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    writePackedRawNoEnsure(prefix0, prefix1, prefixLength);
+    buffer[position++] = (byte) '"';
+    writeLongFieldNoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
+  public void writeLongAsStringField(
+      long namePrefix0,
+      long namePrefix1,
+      long commaPrefix0,
+      long commaPrefix1,
+      int namePrefixLength,
+      int commaPrefixLength,
+      int index,
+      long value) {
+    if (index == 0) {
+      writeLongAsStringField(namePrefix0, namePrefix1, namePrefixLength, 
value);
+    } else {
+      writeLongAsStringField(commaPrefix0, commaPrefix1, commaPrefixLength, 
value);
+    }
+  }
+
+  public void writeObjectStartWithLongAsStringField(byte[] namePrefix, long 
value) {
+    enterDepth();
+    int additional = namePrefix.length + 23;
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    buffer[position++] = (byte) '{';
+    writeRawNoEnsure(namePrefix);
+    buffer[position++] = (byte) '"';
+    writeLongFieldNoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
+  public void writeObjectStartWithLongAsStringField(
+      long prefix0, long prefix1, int prefixLength, long value) {
+    enterDepth();
+    int additional = Math.max(packedPrefixSize(prefixLength), prefixLength + 
23);
+    if (position + additional > buffer.length) {
+      grow(additional);
+    }
+    buffer[position++] = (byte) '{';
+    writePackedRawNoEnsure(prefix0, prefix1, prefixLength);
+    buffer[position++] = (byte) '"';
+    writeLongFieldNoEnsure(value);
+    buffer[position++] = (byte) '"';
+  }
+
   public void writeObjectStartWithRawValue(long prefix0, long prefix1, int 
prefixLength) {
     // Generated codecs prepack '{' with the first field prefix so this 
writer-owned buffer update
     // needs one capacity check. Keep the String value in the generated 
caller: writeString is a
@@ -1175,6 +1272,119 @@ public final class Utf8JsonWriter extends JsonWriter 
implements Appendable {
     exitDepth();
   }
 
+  /**
+   * Writes a long array whose elements are quoted decimal strings.
+   *
+   * <p>This intentionally preserves the independent, pair-unrolled 
generated-caller boundary of
+   * {@link #writeLongArray(long[])}. Keep the signed-Long formatting in each 
lane so generated
+   * callers do not absorb the complete array loop after a small element 
helper is inlined.
+   */
+  public void writeLongArrayAsString(long[] values) {
+    enterDepth();
+    if (position + 2 > buffer.length) {
+      grow(2);
+    }
+    buffer[position++] = '[';
+    int length = values.length;
+    if (length != 0) {
+      if (position + 24 > buffer.length) {
+        grow(24);
+      }
+      buffer[position++] = '"';
+      {
+        long value = values[0];
+        if (value == Long.MIN_VALUE) {
+          writeRawNoEnsure(MIN_LONG_BYTES);
+        } else {
+          if (value < 0) {
+            buffer[position++] = (byte) '-';
+            value = -value;
+          }
+          if (value <= Integer.MAX_VALUE) {
+            writePositiveIntNoEnsure((int) value);
+          } else {
+            position = writePositiveLong(buffer, position, value);
+          }
+        }
+      }
+      buffer[position++] = '"';
+
+      int i = 1;
+      if ((length & 1) == 0) {
+        if (position + 24 > buffer.length) {
+          grow(24);
+        }
+        buffer[position++] = ',';
+        buffer[position++] = '"';
+        {
+          long value = values[i];
+          if (value == Long.MIN_VALUE) {
+            writeRawNoEnsure(MIN_LONG_BYTES);
+          } else {
+            if (value < 0) {
+              buffer[position++] = (byte) '-';
+              value = -value;
+            }
+            if (value <= Integer.MAX_VALUE) {
+              writePositiveIntNoEnsure((int) value);
+            } else {
+              position = writePositiveLong(buffer, position, value);
+            }
+          }
+        }
+        buffer[position++] = '"';
+        i++;
+      }
+
+      for (; i < length; i += 2) {
+        if (position + 48 > buffer.length) {
+          grow(48);
+        }
+        buffer[position++] = ',';
+        buffer[position++] = '"';
+        {
+          long value = values[i];
+          if (value == Long.MIN_VALUE) {
+            writeRawNoEnsure(MIN_LONG_BYTES);
+          } else {
+            if (value < 0) {
+              buffer[position++] = (byte) '-';
+              value = -value;
+            }
+            if (value <= Integer.MAX_VALUE) {
+              writePositiveIntNoEnsure((int) value);
+            } else {
+              position = writePositiveLong(buffer, position, value);
+            }
+          }
+        }
+        buffer[position++] = '"';
+
+        buffer[position++] = ',';
+        buffer[position++] = '"';
+        {
+          long value = values[i + 1];
+          if (value == Long.MIN_VALUE) {
+            writeRawNoEnsure(MIN_LONG_BYTES);
+          } else {
+            if (value < 0) {
+              buffer[position++] = (byte) '-';
+              value = -value;
+            }
+            if (value <= Integer.MAX_VALUE) {
+              writePositiveIntNoEnsure((int) value);
+            } else {
+              position = writePositiveLong(buffer, position, value);
+            }
+          }
+        }
+        buffer[position++] = '"';
+      }
+    }
+    buffer[position++] = ']';
+    exitDepth();
+  }
+
   public void writeStringElement(int index, String value) {
     writeStringElementWithComma(index == 0 ? 0 : 1, value);
   }
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
index 65e923db9..7be44bb4d 100644
--- 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
+++ 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
@@ -1316,6 +1316,7 @@ public class JsonAsyncCompilationTest {
       throws Exception {
     JsonConfig config =
         new JsonConfig(
+            false,
             false,
             true,
             true,
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
index 619b2dc5a..07ee115fd 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
@@ -128,9 +128,10 @@ public class JsonContainerTest extends ForyJsonTestModels {
   @Test
   public void unsignedArrayOverflow() {
     byte[] input = "[4294967295]".getBytes(StandardCharsets.UTF_8);
-    ArrayCodec<byte[]> uint8 = 
ArrayCodec.createUnsignedPrimitive(byte[].class, Types.UINT8_ARRAY);
+    ArrayCodec<byte[]> uint8 =
+        ArrayCodec.createUnsignedPrimitive(byte[].class, Types.UINT8_ARRAY, 
false);
     ArrayCodec<short[]> uint16 =
-        ArrayCodec.createUnsignedPrimitive(short[].class, Types.UINT16_ARRAY);
+        ArrayCodec.createUnsignedPrimitive(short[].class, Types.UINT16_ARRAY, 
false);
 
     assertThrows(ForyJsonException.class, () -> 
uint8.readUtf8(newUtf8Reader(input)));
     assertThrows(ForyJsonException.class, () -> 
uint8.readLatin1(newLatin1Reader(input)));
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonLongAsStringTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonLongAsStringTest.java
new file mode 100644
index 000000000..466f06673
--- /dev/null
+++ 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonLongAsStringTest.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.json;
+
+import static org.apache.fory.json.JsonTestSupport.generatedUtf8WriterClass;
+import static org.apache.fory.json.JsonTestSupport.newStringWriter;
+import static org.apache.fory.json.JsonTestSupport.newUtf8Reader;
+import static org.apache.fory.json.JsonTestSupport.newUtf8Writer;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertThrows;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalLong;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fory.json.annotation.JsonCodec;
+import org.apache.fory.json.annotation.JsonProperty;
+import org.apache.fory.json.codec.AbstractJsonValueCodec;
+import org.apache.fory.json.reader.JsonReader;
+import org.apache.fory.json.writer.JsonWriter;
+import org.apache.fory.json.writer.StringJsonWriter;
+import org.apache.fory.json.writer.Utf8JsonWriter;
+import org.apache.fory.reflect.TypeRef;
+import org.testng.annotations.Test;
+
+public class JsonLongAsStringTest extends ForyJsonTestModels {
+  private static final long UNSAFE_INTEGER = 9_007_199_254_740_992L;
+
+  @Test(dataProvider = "enableCodegen")
+  public void writesLongBindings(boolean codegen) {
+    ForyJson json = 
newJsonBuilder(codegen).writeLongAsString(true).withConcurrencyLevel(1).build();
+    LongValues value = LongValues.create();
+    String expectedPrefix =
+        
"{\"aFirst\":\"-9223372036854775808\",\"boxed\":\"9223372036854775807\","
+            + 
"\"boxedArray\":[\"-1\",null,\"0\"],\"dynamic\":\"9007199254740992\","
+            + "\"list\":[\"1\",\"9007199254740992\"],\"map\":{\"max\":"
+            + 
"\"9223372036854775807\"},\"primitiveArray\":[\"-9223372036854775808\","
+            + "\"0\",\"9223372036854775807\"],\"text\":\"雪\",";
+    String expected = expectedPrefix + "\"\\u603b\\u6570\":\"7\"}";
+
+    String stringJson = json.toJson(value);
+    String utf8Json = new String(json.toJsonBytes(value), 
StandardCharsets.UTF_8);
+    assertEquals(normalizeUnicodeName(stringJson), expected);
+    assertEquals(normalizeUnicodeName(utf8Json), expected);
+    assertEquals(json.toJson(Long.MIN_VALUE), "\"-9223372036854775808\"");
+    assertEquals(
+        new String(json.toJsonBytes(Long.MAX_VALUE), StandardCharsets.UTF_8),
+        "\"9223372036854775807\"");
+    assertEquals(json.toJson(new long[] {-1, 0, 1}), "[\"-1\",\"0\",\"1\"]");
+    assertEquals(json.toJson(new Long[] {-1L, null, 1L}), 
"[\"-1\",null,\"1\"]");
+    assertEquals(json.toJson(Arrays.asList(-1L, 1L)), "[\"-1\",\"1\"]");
+
+    LongValues decoded = json.fromJson(stringJson, LongValues.class);
+    assertEquals(decoded.aFirst, Long.MIN_VALUE);
+    assertEquals(decoded.boxed, Long.valueOf(Long.MAX_VALUE));
+    assertEquals(decoded.primitiveArray, value.primitiveArray);
+    assertEquals(decoded.boxedArray, value.boxedArray);
+    assertEquals(decoded.list, value.list);
+    assertEquals(decoded.map, value.map);
+    assertEquals(decoded.unicode, 7L);
+  }
+
+  @Test(dataProvider = "enableCodegen")
+  public void keepsNumericDefault(boolean codegen) {
+    ForyJson json = newJson(codegen);
+    assertEquals(json.toJson(Long.MIN_VALUE), "-9223372036854775808");
+    assertEquals(json.toJson(new long[] {-1, 0, 1}), "[-1,0,1]");
+    assertEquals(json.toJson(Arrays.asList(-1L, 1L)), "[-1,1]");
+    assertEquals(json.toJson(new AtomicLong(Long.MAX_VALUE)), 
"9223372036854775807");
+    assertEquals(json.toJson(new AtomicLongArray(new long[] {-1, 0})), 
"[-1,0]");
+    assertEquals(json.toJson(OptionalLong.of(Long.MIN_VALUE)), 
"-9223372036854775808");
+  }
+
+  @Test(dataProvider = "enableCodegen")
+  public void readsBothTokenShapes(boolean codegen) {
+    ForyJson json = newJson(codegen);
+    assertEquals(json.fromJson("9223372036854775807", long.class), 
Long.MAX_VALUE);
+    assertEquals(json.fromJson("\"9223372036854775807\"", long.class), 
Long.MAX_VALUE);
+    assertEquals(
+        json.fromJson("[\"-9223372036854775808\",0]", long[].class),
+        new long[] {Long.MIN_VALUE, 0});
+    assertEquals(
+        json.fromJson(
+                
"{\"aFirst\":\"-1\",\"boxed\":2,\"boxedArray\":[],\"dynamic\":3,"
+                    + 
"\"list\":[],\"map\":{},\"primitiveArray\":[],\"总数\":\"4\"}",
+                LongValues.class)
+            .unicode,
+        4L);
+  }
+
+  @Test
+  public void writesUnsignedDigitsAsString() {
+    StringJsonWriter stringWriter = newStringWriter();
+    stringWriter.writeUnsignedLongAsString(-1L);
+    assertEquals(stringWriter.toJson(), "\"18446744073709551615\"");
+
+    Utf8JsonWriter utf8Writer = newUtf8Writer();
+    utf8Writer.writeUnsignedLongAsString(-1L);
+    assertEquals(
+        new String(utf8Writer.toJsonBytes(), StandardCharsets.UTF_8), 
"\"18446744073709551615\"");
+
+    assertEquals(
+        
newUtf8Reader("\"18446744073709551615\"".getBytes(StandardCharsets.UTF_8))
+            .readUnsignedLong(),
+        -1L);
+    assertThrows(
+        ForyJsonException.class,
+        () ->
+            
newUtf8Reader("\"18446744073709551616\"".getBytes(StandardCharsets.UTF_8))
+                .readUnsignedLong());
+  }
+
+  @Test
+  public void snapshotsBuilderSetting() {
+    ForyJsonBuilder builder = 
ForyJson.builder().withCodegen(false).writeLongAsString(false);
+    ForyJson numeric = builder.build();
+    ForyJson quoted = builder.writeLongAsString(true).build();
+    builder.writeLongAsString(false);
+
+    assertEquals(numeric.toJson(7L), "7");
+    assertEquals(quoted.toJson(7L), "\"7\"");
+  }
+
+  @Test(dataProvider = "enableCodegen")
+  public void writesLongWrappers(boolean codegen) {
+    ForyJson json = newJsonBuilder(codegen).writeLongAsString(true).build();
+    LongWrappers value = LongWrappers.create();
+    String expected =
+        "{\"atomic\":\"9223372036854775807\",\"atomicArray\":[\"-1\",\"0\"],"
+            + "\"optional\":\"9007199254740992\","
+            + "\"optionalLong\":\"-9223372036854775808\",\"reference\":\"7\"}";
+
+    assertEquals(json.toJson(value), expected);
+    assertEquals(new String(json.toJsonBytes(value), StandardCharsets.UTF_8), 
expected);
+    assertEquals(json.toJson(new AtomicLong(Long.MAX_VALUE)), 
"\"9223372036854775807\"");
+    assertEquals(json.toJson(OptionalLong.of(Long.MIN_VALUE)), 
"\"-9223372036854775808\"");
+    assertEquals(
+        json.toJson(Optional.of(Long.MAX_VALUE), new TypeRef<Optional<Long>>() 
{}),
+        "\"9223372036854775807\"");
+
+    LongWrappers decoded = json.fromJson(expected, LongWrappers.class);
+    assertEquals(decoded.atomic.get(), Long.MAX_VALUE);
+    assertEquals(decoded.atomicArray.get(0), -1L);
+    assertEquals(decoded.atomicArray.get(1), 0L);
+    assertEquals(decoded.optional, Optional.of(UNSAFE_INTEGER));
+    assertEquals(decoded.optionalLong, OptionalLong.of(Long.MIN_VALUE));
+    assertEquals(decoded.reference.get(), Long.valueOf(7L));
+  }
+
+  @Test
+  public void keepsOtherLongCarriers() {
+    ForyJson numeric = ForyJson.builder().withCodegen(false).build();
+    ForyJson quoted = 
ForyJson.builder().withCodegen(false).writeLongAsString(true).build();
+    Object[] values = {
+      new Date(123456789L),
+      BitSet.valueOf(new long[] {Long.MIN_VALUE}),
+      new NumberValue(Long.MAX_VALUE)
+    };
+    for (Object value : values) {
+      assertEquals(quoted.toJson(value), numeric.toJson(value), 
value.getClass().getName());
+    }
+    assertEquals(
+        quoted.toJson(new CustomValue(Long.MAX_VALUE)), 
"{\"value\":\"long:9223372036854775807\"}");
+  }
+
+  @Test
+  public void isolatesGeneratedWriters() {
+    LongValues value = LongValues.create();
+    ForyJson numeric = ForyJson.builder().withAsyncCompilation(false).build();
+    ForyJson quoted =
+        
ForyJson.builder().writeLongAsString(true).withAsyncCompilation(false).build();
+
+    assertEquals(numeric.toJsonBytes(value)[10], (byte) '-');
+    assertEquals(quoted.toJson(value).charAt(10), '"');
+    assertNotSame(
+        generatedUtf8WriterClass(numeric, LongValues.class),
+        generatedUtf8WriterClass(quoted, LongValues.class));
+  }
+
+  public static final class LongValues {
+    @JsonProperty(index = 0)
+    public long aFirst;
+
+    @JsonProperty(index = 1)
+    public Long boxed;
+
+    @JsonProperty(index = 2)
+    public Long[] boxedArray;
+
+    @JsonProperty(index = 3)
+    public Object dynamic;
+
+    @JsonProperty(index = 4)
+    public List<Long> list;
+
+    @JsonProperty(index = 5)
+    public Map<String, Long> map;
+
+    @JsonProperty(index = 6)
+    public long[] primitiveArray;
+
+    @JsonProperty(index = 7)
+    public String text;
+
+    @JsonProperty(value = "总数", index = 8)
+    public long unicode;
+
+    static LongValues create() {
+      LongValues value = new LongValues();
+      value.aFirst = Long.MIN_VALUE;
+      value.boxed = Long.MAX_VALUE;
+      value.boxedArray = new Long[] {-1L, null, 0L};
+      value.dynamic = UNSAFE_INTEGER;
+      value.list = Arrays.asList(1L, UNSAFE_INTEGER);
+      value.map = new LinkedHashMap<>();
+      value.map.put("max", Long.MAX_VALUE);
+      value.primitiveArray = new long[] {Long.MIN_VALUE, 0, Long.MAX_VALUE};
+      value.text = "雪";
+      value.unicode = 7;
+      return value;
+    }
+  }
+
+  private static String normalizeUnicodeName(String json) {
+    return json.replace("\"总数\"", "\"\\u603b\\u6570\"");
+  }
+
+  public static final class NumberValue {
+    public Number value;
+
+    NumberValue(long value) {
+      this.value = value;
+    }
+  }
+
+  public static final class LongWrappers {
+    @JsonProperty(index = 0)
+    public AtomicLong atomic;
+
+    @JsonProperty(index = 1)
+    public AtomicLongArray atomicArray;
+
+    @JsonProperty(index = 2)
+    public Optional<Long> optional;
+
+    @JsonProperty(index = 3)
+    public OptionalLong optionalLong;
+
+    @JsonProperty(index = 4)
+    public AtomicReference<Long> reference;
+
+    static LongWrappers create() {
+      LongWrappers value = new LongWrappers();
+      value.atomic = new AtomicLong(Long.MAX_VALUE);
+      value.atomicArray = new AtomicLongArray(new long[] {-1, 0});
+      value.optional = Optional.of(UNSAFE_INTEGER);
+      value.optionalLong = OptionalLong.of(Long.MIN_VALUE);
+      value.reference = new AtomicReference<>(7L);
+      return value;
+    }
+  }
+
+  public static final class CustomValue {
+    @JsonCodec(LongTextCodec.class)
+    public long value;
+
+    CustomValue(long value) {
+      this.value = value;
+    }
+  }
+
+  public static final class LongTextCodec extends AbstractJsonValueCodec<Long> 
{
+    @Override
+    public void write(JsonWriter writer, Long value) {
+      writer.writeString("long:" + value);
+    }
+
+    @Override
+    public Long read(JsonReader reader) {
+      return Long.valueOf(reader.readString().substring(5));
+    }
+  }
+}
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonTestSupport.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonTestSupport.java
index 503ea4851..889d30e71 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonTestSupport.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonTestSupport.java
@@ -45,6 +45,7 @@ final class JsonTestSupport {
           false,
           false,
           false,
+          false,
           true,
           PropertyNamingStrategy.LOWER_CAMEL_CASE,
           JsonTestSupport.class.getClassLoader(),
diff --git 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinJsonCodecFactory.kt
 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinJsonCodecFactory.kt
index 70c2e8a12..1ff68727c 100644
--- 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinJsonCodecFactory.kt
+++ 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinJsonCodecFactory.kt
@@ -43,13 +43,15 @@ internal object KotlinJsonCodecFactory : JsonCodecFactory {
         semanticId,
         !rawType.isPrimitive,
         type.typeExtMeta.nullable(),
+        resolver.writeLongAsString(),
       )
     }
     if (semanticId in Types.UINT8_ARRAY..Types.UINT64_ARRAY) {
-      KotlinUnsignedArrayCodecs.create(rawType, semanticId)?.let {
+      val writeLongAsString = resolver.writeLongAsString()
+      KotlinUnsignedArrayCodecs.create(rawType, semanticId, 
writeLongAsString)?.let {
         return it
       }
-      return ArrayCodec.createUnsignedPrimitive(rawType, semanticId)
+      return ArrayCodec.createUnsignedPrimitive(rawType, semanticId, 
writeLongAsString)
     }
     if (rawType == Unit::class.java) {
       return if (type.typeExtMeta?.nullable() == true) {
diff --git 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedArrayCodecs.kt
 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedArrayCodecs.kt
index 724764bf4..a2e6961d0 100644
--- 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedArrayCodecs.kt
+++ 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedArrayCodecs.kt
@@ -40,12 +40,22 @@ import org.apache.fory.type.Types
  */
 @OptIn(ExperimentalUnsignedTypes::class)
 internal object KotlinUnsignedArrayCodecs {
-  fun create(rawType: Class<*>, typeId: Int): JsonValueCodec<*>? =
+  fun create(
+    rawType: Class<*>,
+    typeId: Int,
+    writeLongAsString: Boolean,
+  ): JsonValueCodec<*>? =
     when (rawType) {
       UByteArray::class.java -> requireType(rawType, typeId, 
Types.UINT8_ARRAY, UByteArrayCodec)
       UShortArray::class.java -> requireType(rawType, typeId, 
Types.UINT16_ARRAY, UShortArrayCodec)
       UIntArray::class.java -> requireType(rawType, typeId, 
Types.UINT32_ARRAY, UIntArrayCodec)
-      ULongArray::class.java -> requireType(rawType, typeId, 
Types.UINT64_ARRAY, ULongArrayCodec)
+      ULongArray::class.java ->
+        requireType(
+          rawType,
+          typeId,
+          Types.UINT64_ARRAY,
+          if (writeLongAsString) ULongArrayCodec.QUOTED else 
ULongArrayCodec.NUMERIC,
+        )
       else -> null
     }
 
@@ -65,7 +75,7 @@ internal object KotlinUnsignedArrayCodecs {
 
   private object UByteArrayCodec : JsonValueCodec<UByteArray> {
     private val delegate =
-      ArrayCodec.createUnsignedPrimitive(ByteArray::class.java, 
Types.UINT8_ARRAY)
+      ArrayCodec.createUnsignedPrimitive(ByteArray::class.java, 
Types.UINT8_ARRAY, false)
     private val wrapperBytes = 
GraphMemoryEstimates.shallowObjectBytes(UByteArray::class.java)
 
     override fun writeString(writer: StringJsonWriter, value: UByteArray?) =
@@ -95,7 +105,7 @@ internal object KotlinUnsignedArrayCodecs {
 
   private object UShortArrayCodec : JsonValueCodec<UShortArray> {
     private val delegate =
-      ArrayCodec.createUnsignedPrimitive(ShortArray::class.java, 
Types.UINT16_ARRAY)
+      ArrayCodec.createUnsignedPrimitive(ShortArray::class.java, 
Types.UINT16_ARRAY, false)
     private val wrapperBytes = 
GraphMemoryEstimates.shallowObjectBytes(UShortArray::class.java)
 
     override fun writeString(writer: StringJsonWriter, value: UShortArray?) =
@@ -125,7 +135,7 @@ internal object KotlinUnsignedArrayCodecs {
 
   private object UIntArrayCodec : JsonValueCodec<UIntArray> {
     private val delegate =
-      ArrayCodec.createUnsignedPrimitive(IntArray::class.java, 
Types.UINT32_ARRAY)
+      ArrayCodec.createUnsignedPrimitive(IntArray::class.java, 
Types.UINT32_ARRAY, false)
     private val wrapperBytes = 
GraphMemoryEstimates.shallowObjectBytes(UIntArray::class.java)
 
     override fun writeString(writer: StringJsonWriter, value: UIntArray?) =
@@ -150,9 +160,20 @@ internal object KotlinUnsignedArrayCodecs {
     }
   }
 
-  private object ULongArrayCodec : JsonValueCodec<ULongArray> {
-    private val delegate =
-      ArrayCodec.createUnsignedPrimitive(LongArray::class.java, 
Types.UINT64_ARRAY)
+  private class ULongArrayCodec(
+    private val delegate: JsonValueCodec<LongArray>,
+  ) : JsonValueCodec<ULongArray> {
+    companion object {
+      val NUMERIC =
+        ULongArrayCodec(
+          ArrayCodec.createUnsignedPrimitive(LongArray::class.java, 
Types.UINT64_ARRAY, false)
+        )
+      val QUOTED =
+        ULongArrayCodec(
+          ArrayCodec.createUnsignedPrimitive(LongArray::class.java, 
Types.UINT64_ARRAY, true)
+        )
+    }
+
     private val wrapperBytes = 
GraphMemoryEstimates.shallowObjectBytes(ULongArray::class.java)
 
     override fun writeString(writer: StringJsonWriter, value: ULongArray?) =
diff --git 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedCodecs.kt
 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedCodecs.kt
index f48524201..054758d67 100644
--- 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedCodecs.kt
+++ 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinUnsignedCodecs.kt
@@ -35,7 +35,12 @@ import org.apache.fory.type.Types
 /** Width- and carrier-specialized codecs for Kotlin unsigned scalars. */
 @OptIn(ExperimentalUnsignedTypes::class)
 internal object KotlinUnsignedCodecs {
-  fun scalar(typeId: Int, boxedResult: Boolean, nullable: Boolean): 
JsonValueCodec<Any?> =
+  fun scalar(
+    typeId: Int,
+    boxedResult: Boolean,
+    nullable: Boolean,
+    writeLongAsString: Boolean,
+  ): JsonValueCodec<Any?> =
     when (typeId) {
       Types.UINT8 ->
         if (nullable) UByteCodec.NULLABLE
@@ -46,8 +51,13 @@ internal object KotlinUnsignedCodecs {
       Types.UINT32 ->
         if (nullable) UIntCodec.NULLABLE else if (boxedResult) UIntCodec.BOXED 
else UIntCarrierCodec
       Types.UINT64 ->
-        if (nullable) ULongCodec.NULLABLE
-        else if (boxedResult) ULongCodec.BOXED else ULongCarrierCodec
+        if (writeLongAsString) {
+          if (nullable) ULongAsStringCodec.NULLABLE
+          else if (boxedResult) ULongAsStringCodec.BOXED else 
ULongAsStringCarrierCodec
+        } else {
+          if (nullable) ULongCodec.NULLABLE
+          else if (boxedResult) ULongCodec.BOXED else ULongCarrierCodec
+        }
       else -> throw ForyJsonException("Unknown Kotlin unsigned JSON type id 
$typeId")
     }
 
@@ -93,6 +103,11 @@ internal object KotlinUnsignedCodecs {
   @JvmName("writeULongRaw")
   fun writeULongRaw(writer: JsonWriter, value: Long) = 
writer.writeUnsignedLong(value)
 
+  @JvmStatic
+  @JvmName("writeULongAsStringRaw")
+  fun writeULongAsStringRaw(writer: JsonWriter, value: Long) =
+    writer.writeUnsignedLongAsString(value)
+
   private class UByteCodec(private val nullable: Boolean) : 
JsonValueCodec<Any?> {
     companion object {
       val BOXED: JsonValueCodec<Any?> = UByteCodec(false)
@@ -377,6 +392,77 @@ internal object KotlinUnsignedCodecs {
     override fun writeCarrierMethod(): Method = Methods.write
   }
 
+  private class ULongAsStringCodec(private val nullable: Boolean) : 
JsonValueCodec<Any?> {
+    companion object {
+      val BOXED: JsonValueCodec<Any?> = ULongAsStringCodec(false)
+      val NULLABLE: JsonValueCodec<Any?> = ULongAsStringCodec(true)
+    }
+
+    override fun writeString(writer: StringJsonWriter, value: Any?) = 
write(writer, value)
+
+    override fun writeUtf8(writer: Utf8JsonWriter, value: Any?) = 
write(writer, value)
+
+    override fun readLatin1(reader: Latin1JsonReader): Any? = read(reader)
+
+    override fun readUtf16(reader: Utf16JsonReader): Any? = read(reader)
+
+    override fun readUtf8(reader: Utf8JsonReader): Any? = read(reader)
+
+    private fun write(writer: JsonWriter, value: Any?) {
+      if (value == null) {
+        writeNull(writer, nullable)
+        return
+      }
+      writeULongAsStringRaw(writer, (value as ULong).toLong())
+    }
+
+    private fun read(reader: JsonReader): Any? {
+      if (reader.tryReadNullToken()) return readNull(nullable)
+      return readULongRaw(reader).toULong()
+    }
+  }
+
+  private object ULongAsStringCarrierCodec : JsonValueCodec<Any?>, 
DirectUnboxedValueCodec {
+    private object Methods {
+      val read: Method =
+        KotlinUnsignedCodecs::class.java.getMethod("readULongRaw", 
JsonReader::class.java)
+      val write: Method =
+        KotlinUnsignedCodecs::class
+          .java
+          .getMethod("writeULongAsStringRaw", JsonWriter::class.java, 
java.lang.Long.TYPE)
+    }
+
+    override fun writeString(writer: StringJsonWriter, value: Any?) =
+      writeStringCarrier(writer, value ?: rejectNull())
+
+    override fun writeUtf8(writer: Utf8JsonWriter, value: Any?) =
+      writeUtf8Carrier(writer, value ?: rejectNull())
+
+    override fun readLatin1(reader: Latin1JsonReader): Any = 
readLatin1Carrier(reader)
+
+    override fun readUtf16(reader: Utf16JsonReader): Any = 
readUtf16Carrier(reader)
+
+    override fun readUtf8(reader: Utf8JsonReader): Any = 
readUtf8Carrier(reader)
+
+    override fun carrierType(): Class<*> = java.lang.Long.TYPE
+
+    override fun readLatin1Carrier(reader: Latin1JsonReader): Any = 
readULongRaw(reader)
+
+    override fun readUtf16Carrier(reader: Utf16JsonReader): Any = 
readULongRaw(reader)
+
+    override fun readUtf8Carrier(reader: Utf8JsonReader): Any = 
readULongRaw(reader)
+
+    override fun writeStringCarrier(writer: StringJsonWriter, carrier: Any) =
+      writeULongAsStringRaw(writer, carrier as Long)
+
+    override fun writeUtf8Carrier(writer: Utf8JsonWriter, carrier: Any) =
+      writeULongAsStringRaw(writer, carrier as Long)
+
+    override fun readCarrierMethod(): Method = Methods.read
+
+    override fun writeCarrierMethod(): Method = Methods.write
+  }
+
   private fun writeNull(writer: JsonWriter, nullable: Boolean) {
     if (!nullable) rejectNull()
     writer.writeNull()
diff --git 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinValueClassPrimitiveCodecs.kt
 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinValueClassPrimitiveCodecs.kt
index 8d756b0b4..f5af42979 100644
--- 
a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinValueClassPrimitiveCodecs.kt
+++ 
b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinValueClassPrimitiveCodecs.kt
@@ -51,7 +51,7 @@ internal object KotlinValueClassCapabilities {
       }
       java.lang.Byte.TYPE -> {
         if (typeId == Types.UINT8) {
-          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false)
+          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false, 
false)
           if (exact(child, expected)) {
             operations.typed<KotlinByteValueClassOperations<Any>>()?.let {
               return UByteCapability(it)
@@ -65,7 +65,7 @@ internal object KotlinValueClassCapabilities {
       }
       java.lang.Short.TYPE -> {
         if (typeId == Types.UINT16) {
-          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false)
+          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false, 
false)
           if (exact(child, expected)) {
             operations.typed<KotlinShortValueClassOperations<Any>>()?.let {
               return UShortCapability(it)
@@ -79,7 +79,7 @@ internal object KotlinValueClassCapabilities {
       }
       java.lang.Integer.TYPE -> {
         if (typeId == Types.UINT32) {
-          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false)
+          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false, 
false)
           if (exact(child, expected)) {
             operations.typed<KotlinIntValueClassOperations<Any>>()?.let {
               return UIntCapability(it)
@@ -93,16 +93,28 @@ internal object KotlinValueClassCapabilities {
       }
       java.lang.Long.TYPE -> {
         if (typeId == Types.UINT64) {
-          val expected = KotlinUnsignedCodecs.scalar(typeId, false, false)
-          if (exact(child, expected)) {
+          val numeric = KotlinUnsignedCodecs.scalar(typeId, false, false, 
false)
+          if (exact(child, numeric)) {
             operations.typed<KotlinLongValueClassOperations<Any>>()?.let {
               return ULongCapability(it)
             }
           }
+          val quoted = KotlinUnsignedCodecs.scalar(typeId, false, false, true)
+          if (exact(child, quoted)) {
+            operations.typed<KotlinLongValueClassOperations<Any>>()?.let {
+              return ULongAsStringCapability(it)
+            }
+          }
         } else if (typeId == Types.UNKNOWN && exact(child, 
ScalarCodecs.LongCodec.PRIMITIVE)) {
           operations.typed<KotlinLongValueClassOperations<Any>>()?.let {
             return LongCapability(it)
           }
+        } else if (
+          typeId == Types.UNKNOWN && exact(child, 
ScalarCodecs.LongAsStringCodec.PRIMITIVE)
+        ) {
+          operations.typed<KotlinLongValueClassOperations<Any>>()?.let {
+            return LongAsStringCapability(it)
+          }
         }
       }
       java.lang.Float.TYPE ->
@@ -234,6 +246,25 @@ private class LongCapability(
     operations.constructLong(reader, reader.readLongValue())
 }
 
+private class LongAsStringCapability(
+  private val operations: KotlinLongValueClassOperations<Any>,
+) : KotlinValueClassCapability {
+  override fun writeString(writer: StringJsonWriter, value: Any) =
+    writer.writeLongAsString(operations.unboxLong(value))
+
+  override fun writeUtf8(writer: Utf8JsonWriter, value: Any) =
+    writer.writeLongAsString(operations.unboxLong(value))
+
+  override fun readLatin1(reader: Latin1JsonReader): Any =
+    operations.constructLong(reader, reader.readLongValue())
+
+  override fun readUtf16(reader: Utf16JsonReader): Any =
+    operations.constructLong(reader, reader.readLongValue())
+
+  override fun readUtf8(reader: Utf8JsonReader): Any =
+    operations.constructLong(reader, reader.readLongValue())
+}
+
 private class FloatCapability(
   private val operations: KotlinFloatValueClassOperations<Any>,
 ) : KotlinValueClassCapability {
@@ -366,3 +397,22 @@ private class ULongCapability(
   override fun readUtf8(reader: Utf8JsonReader): Any =
     operations.constructLong(reader, KotlinUnsignedCodecs.readULongRaw(reader))
 }
+
+private class ULongAsStringCapability(
+  private val operations: KotlinLongValueClassOperations<Any>,
+) : KotlinValueClassCapability {
+  override fun writeString(writer: StringJsonWriter, value: Any) =
+    KotlinUnsignedCodecs.writeULongAsStringRaw(writer, 
operations.unboxLong(value))
+
+  override fun writeUtf8(writer: Utf8JsonWriter, value: Any) =
+    KotlinUnsignedCodecs.writeULongAsStringRaw(writer, 
operations.unboxLong(value))
+
+  override fun readLatin1(reader: Latin1JsonReader): Any =
+    operations.constructLong(reader, KotlinUnsignedCodecs.readULongRaw(reader))
+
+  override fun readUtf16(reader: Utf16JsonReader): Any =
+    operations.constructLong(reader, KotlinUnsignedCodecs.readULongRaw(reader))
+
+  override fun readUtf8(reader: Utf8JsonReader): Any =
+    operations.constructLong(reader, KotlinUnsignedCodecs.readULongRaw(reader))
+}
diff --git 
a/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/ForyJsonKotlinTest.kt
 
b/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/ForyJsonKotlinTest.kt
index 662e20d3c..957830806 100644
--- 
a/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/ForyJsonKotlinTest.kt
+++ 
b/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/ForyJsonKotlinTest.kt
@@ -19,9 +19,11 @@
 
 package org.apache.fory.json.kotlin
 
+import java.util.concurrent.atomic.AtomicLong
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 import org.apache.fory.json.ForyJsonException
@@ -47,6 +49,24 @@ class ForyJsonKotlinTest {
 
   data class UnsignedValues(val count: UInt, val total: ULong, val optional: 
UInt?)
 
+  @JvmInline value class SignedLongId(val value: Long)
+
+  @JvmInline value class UnsignedLongId(val value: ULong)
+
+  @OptIn(ExperimentalUnsignedTypes::class)
+  data class LongStringValues(
+    val signed: Long,
+    val signedNullable: Long?,
+    val unsigned: ULong,
+    val nullable: ULong?,
+    val unsignedArray: ULongArray,
+    val unsignedList: List<ULong>,
+    val unsignedMap: Map<String, ULong>,
+    val signedId: SignedLongId,
+    val unsignedId: UnsignedLongId,
+    val atomic: AtomicLong,
+  )
+
   object Marker
 
   @Test
@@ -136,6 +156,108 @@ class ForyJsonKotlinTest {
     assertEquals(listOf(0u, UInt.MAX_VALUE), fory.fromJson("[0,4294967295]", 
listType))
   }
 
+  @OptIn(ExperimentalUnsignedTypes::class)
+  @Test
+  fun longAsString() {
+    val value =
+      LongStringValues(
+        Long.MIN_VALUE,
+        9_007_199_254_740_992L,
+        ULong.MAX_VALUE,
+        7uL,
+        ulongArrayOf(0uL, ULong.MAX_VALUE),
+        listOf(1uL, ULong.MAX_VALUE),
+        linkedMapOf("max" to ULong.MAX_VALUE),
+        SignedLongId(Long.MAX_VALUE),
+        UnsignedLongId(ULong.MAX_VALUE),
+        AtomicLong(Long.MAX_VALUE),
+      )
+    val type = jsonTypeRef<LongStringValues>()
+    for (mode in listOf(KotlinJsonTestMode.INTERPRETED, 
KotlinJsonTestMode.SYNCHRONOUS)) {
+      val json = newKotlinJson(mode) { writeLongAsString(true) }
+      val encoded = json.toJson(value, type)
+      assertTrue(encoded.contains("\"signed\":\"-9223372036854775808\""), 
encoded)
+      assertTrue(encoded.contains("\"signedNullable\":\"9007199254740992\""), 
encoded)
+      assertTrue(encoded.contains("\"unsigned\":\"18446744073709551615\""), 
encoded)
+      assertTrue(encoded.contains("\"nullable\":\"7\""), encoded)
+      assertTrue(
+        encoded.contains("\"unsignedArray\":[\"0\",\"18446744073709551615\"]"),
+        encoded,
+      )
+      assertTrue(
+        encoded.contains("\"unsignedList\":[\"1\",\"18446744073709551615\"]"),
+        encoded,
+      )
+      assertTrue(
+        encoded.contains("\"unsignedMap\":{\"max\":\"18446744073709551615\"}"),
+        encoded,
+      )
+      assertTrue(encoded.contains("\"signedId\":\"9223372036854775807\""), 
encoded)
+      assertTrue(
+        encoded.contains("\"unsignedId\":\"18446744073709551615\""),
+        encoded,
+      )
+      assertTrue(encoded.contains("\"atomic\":\"9223372036854775807\""), 
encoded)
+      val decoded = json.fromJson(encoded, type)
+      assertEquals(value.signed, decoded.signed)
+      assertEquals(value.signedNullable, decoded.signedNullable)
+      assertEquals(value.unsigned, decoded.unsigned)
+      assertEquals(value.nullable, decoded.nullable)
+      assertTrue(value.unsignedArray.contentEquals(decoded.unsignedArray))
+      assertEquals(value.unsignedList, decoded.unsignedList)
+      assertEquals(value.unsignedMap, decoded.unsignedMap)
+      assertEquals(value.signedId, decoded.signedId)
+      assertEquals(value.unsignedId, decoded.unsignedId)
+      assertEquals(value.atomic.get(), decoded.atomic.get())
+      assertEquals(encoded, json.toJsonBytes(value, 
type).toString(Charsets.UTF_8))
+      assertEquals(ULong.MAX_VALUE, json.fromJson("18446744073709551615", 
jsonTypeRef<ULong>()))
+      assertEquals(
+        ULong.MAX_VALUE,
+        json.fromJson("\"18446744073709551615\"", jsonTypeRef<ULong>()),
+      )
+      assertFailsWith<ForyJsonException> {
+        json.fromJson("\"18446744073709551616\"", jsonTypeRef<ULong>())
+      }
+
+      if (mode == KotlinJsonTestMode.SYNCHRONOUS) {
+        val refs =
+          generatedClassBytes(json, "LongStringVal")
+            .filterKeys { it.contains("WriterForyJsonCodec") }
+            .values
+            .flatMap(::generatedMethodRefs)
+        assertTrue(
+          refs.any {
+            it.owner == "org/apache/fory/json/kotlin/KotlinUnsignedCodecs" &&
+              it.name == "writeULongAsStringRaw" &&
+              it.descriptor == "(Lorg/apache/fory/json/writer/JsonWriter;J)V"
+          },
+          refs.toString(),
+        )
+        assertTrue(
+          refs.any {
+            it.owner == "org/apache/fory/json/writer/StringJsonWriter" &&
+              it.name == "writeLongAsStringField"
+          },
+          refs.toString(),
+        )
+        assertTrue(
+          refs.any {
+            it.owner == "org/apache/fory/json/writer/Utf8JsonWriter" &&
+              it.name == "writeLongAsStringField"
+          },
+          refs.toString(),
+        )
+        assertFalse(
+          refs.any {
+            it.owner == "org/apache/fory/json/resolver/JsonTypeResolver" &&
+              it.name == "writeLongAsString" &&
+              it.descriptor == "()Z"
+          }
+        )
+      }
+    }
+  }
+
   @Test
   fun unsignedMetadata() {
     val model = KotlinMetadataModels.objectModel(jsonTypeRef<UnsignedValues>())
diff --git 
a/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinBuiltInCodecsTest.kt
 
b/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinBuiltInCodecsTest.kt
index e63cbf166..eecabbd1c 100644
--- 
a/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinBuiltInCodecsTest.kt
+++ 
b/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinBuiltInCodecsTest.kt
@@ -617,7 +617,7 @@ class KotlinBuiltInCodecsTest {
     readName: String,
     writeName: String,
   ) {
-    val direct = KotlinUnsignedCodecs.scalar(typeId, false, false) as 
DirectUnboxedValueCodec
+    val direct = KotlinUnsignedCodecs.scalar(typeId, false, false, false) as 
DirectUnboxedValueCodec
     assertEquals(carrier, direct.carrierType())
     val readMethod = direct.readCarrierMethod()
     assertEquals(readName, readMethod.name)
diff --git 
a/scala/fory-json-scala/src/test/scala/org/apache/fory/json/scala/ScalaJsonSuite.scala
 
b/scala/fory-json-scala/src/test/scala/org/apache/fory/json/scala/ScalaJsonSuite.scala
index 18ca60d32..c8c67a8c1 100644
--- 
a/scala/fory-json-scala/src/test/scala/org/apache/fory/json/scala/ScalaJsonSuite.scala
+++ 
b/scala/fory-json-scala/src/test/scala/org/apache/fory/json/scala/ScalaJsonSuite.scala
@@ -20,6 +20,7 @@
 package org.apache.fory.json.scala
 
 import java.nio.charset.StandardCharsets.UTF_8
+import java.util.concurrent.atomic.AtomicLong
 
 import org.apache.fory.json.ForyJsonException
 import org.apache.fory.json.annotation.{JsonIgnore, JsonProperty, 
JsonUnwrapped}
@@ -96,6 +97,16 @@ case class NullableRequired(value: String)
 
 case class UserId(value: Int) extends AnyVal
 
+case class LongId(value: Long) extends AnyVal
+
+case class LongStringValues(
+    aFirst: Long,
+    boxed: java.lang.Long,
+    values: Array[Long],
+    id: LongId,
+    atomic: AtomicLong
+)
+
 case class UnitValue(value: Unit)
 
 case class ExplicitNullable(
@@ -144,6 +155,53 @@ case class CodecSlots(
 )
 
 class ScalaJsonSuite extends AnyFunSuite {
+  test("long as string") {
+    val value =
+      LongStringValues(
+        Long.MinValue,
+        Long.MaxValue,
+        Array(-1L, 0L, Long.MaxValue),
+        LongId(7L),
+        new AtomicLong(Long.MaxValue)
+      )
+    val list = List(1L, 9007199254740992L)
+    val map = Map("max" -> Long.MaxValue)
+    val optional = Some(9007199254740992L): Option[Long]
+    val listType = ScalaTypeRef[List[Long]]
+    val mapType = ScalaTypeRef[Map[String, Long]]
+    val optionType = ScalaTypeRef[Option[Long]]
+    for (json <- Seq(
+        
ForyJsonScala.builder().writeLongAsString(true).withCodegen(false).build(),
+        
ForyJsonScala.builder().writeLongAsString(true).withAsyncCompilation(false).build()
+      )) {
+      val encoded = json.toJson(value)
+      assert(encoded.contains("\"aFirst\":\"-9223372036854775808\""), encoded)
+      assert(encoded.contains("\"boxed\":\"9223372036854775807\""), encoded)
+      assert(
+        encoded.contains("\"values\":[\"-1\",\"0\",\"9223372036854775807\"]"),
+        encoded
+      )
+      assert(encoded.contains("\"id\":\"7\""), encoded)
+      assert(encoded.contains("\"atomic\":\"9223372036854775807\""), encoded)
+      assert(new String(json.toJsonBytes(value), UTF_8) == encoded)
+      assert(json.toJson(list, listType) == "[\"1\",\"9007199254740992\"]")
+      assert(json.toJson(map, mapType) == "{\"max\":\"9223372036854775807\"}")
+      assert(json.toJson(optional, optionType) == "\"9007199254740992\"")
+      assert(json.fromJson("[\"1\",9007199254740992]", listType) == list)
+      assert(json.fromJson("{\"max\":\"9223372036854775807\"}", mapType) == 
map)
+      assert(json.fromJson("\"9007199254740992\"", optionType) == optional)
+
+      val decoded = json.fromJson(encoded, classOf[LongStringValues])
+      assert(decoded.aFirst == value.aFirst)
+      assert(decoded.boxed == value.boxed)
+      assert(decoded.values.sameElements(value.values))
+      assert(decoded.id == value.id)
+      assert(decoded.atomic.get() == value.atomic.get())
+      assert(json.fromJson("\"9223372036854775807\"", classOf[Long]) == 
Long.MaxValue)
+      assert(json.fromJson("9223372036854775807", classOf[Long]) == 
Long.MaxValue)
+    }
+  }
+
   test("case class collections and recursive option") {
     val json = ForyJsonScala.builder().withCodegen(false).build()
     val node = Node(1, Some(Node(2, None)))


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to