Repository: alibaba/fastjson2
Pull request #3225 (2024-12-30 11:06:52)

JSON formatting support using spaces for indentation
Added a new JSONWriter.Feature PrettyFormatWithSpace to format the output using spaces.


Patch:
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
index d2aa6e3fe..f2164d013 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
@@ -30,6 +30,7 @@ public abstract class JSONWriter
         implements Closeable {
     static final long WRITE_ARRAY_NULL_MASK = NullAsDefaultValue.mask | WriteNullListAsEmpty.mask;
     static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+    static final byte PRETTY_NON = 0, PRETTY_TAB = 1, PRETTY_2_SPACE = 2, PRETTY_4_SPACE = 4;
 
     public final Context context;
     public final boolean utf8;
@@ -49,8 +50,7 @@ public abstract class JSONWriter
     protected IdentityHashMap<Object, Path> refs;
     protected Path path;
     protected String lastReference;
-    protected boolean pretty;
-    protected int indent;
+    protected byte pretty;
     protected Object attachment;
 
     protected JSONWriter(
@@ -71,7 +71,15 @@ public abstract class JSONWriter
 
         // 64M or 1G
         maxArraySize = (context.features & LargeObject.mask) != 0 ? 1073741824 : 67108864;
-        pretty = (context.features & PrettyFormat.mask) != 0;
+        if ((context.features & PrettyFormatWith4Space.mask) != 0) {
+            pretty = PRETTY_4_SPACE;
+        } else if ((context.features & PrettyFormatWith2Space.mask) != 0) {
+            pretty = PRETTY_2_SPACE;
+        } else if ((context.features & PrettyFormat.mask) != 0) {
+            pretty = PRETTY_TAB;
+        } else {
+            pretty = PRETTY_NON;
+        }
     }
 
     public final Charset getCharset() {
@@ -682,8 +690,8 @@ public abstract class JSONWriter
     }
 
     public static JSONWriter ofPretty(JSONWriter writer) {
-        if (!writer.pretty) {
-            writer.pretty = true;
+        if (writer.pretty == PRETTY_NON) {
+            writer.pretty = PRETTY_TAB;
             writer.context.features |= PrettyFormat.mask;
         }
         return writer;
@@ -1613,6 +1621,11 @@ public abstract class JSONWriter
             return;
         }
 
+        if (map.isEmpty()) {
+            writeRaw('{', '}');
+            return;
+        }
+
         final long NONE_DIRECT_FEATURES = ReferenceDetection.mask
                 | PrettyFormat.mask
                 | NotWriteEmptyArray.mask
@@ -1642,7 +1655,97 @@ public abstract class JSONWriter
         write0('}');
     }
 
-    public abstract void write(JSONObject map);
+    public void write(JSONObject map) {
+        if (map == null) {
+            this.writeNull();
+            return;
+        }
+
+        if (map.isEmpty()) {
+            writeRaw('{', '}');
+            return;
+        }
+
+        final long NONE_DIRECT_FEATURES = ReferenceDetection.mask
+                | NotWriteEmptyArray.mask
+                | NotWriteDefaultValue.mask;
+
+        if ((context.features & NONE_DIRECT_FEATURES) != 0) {
+            ObjectWriter objectWriter = context.getObjectWriter(map.getClass());
+            objectWriter.write(this, map, null, null, 0);
+            return;
+        }
+
+        startObject();
+
+        boolean first = true;
+        for (Map.Entry entry : map.entrySet()) {
+            Object value = entry.getValue();
+            if (value == null && (context.features & WriteMapNullValue.mask) == 0) {
+                continue;
+            }
+
+            if (!first) {
+                writeComma();
+            }
+
+            first = false;
+            Object key = entry.getKey();
+            if (key instanceof String) {
+                writeString((String) key);
+            } else {
+                writeAny(key);
+            }
+
+            writeColon();
+
+            if (value == null) {
+                writeNull();
+                continue;
+            }
+
+            Class<?> valueClass = value.getClass();
+            if (valueClass == String.class) {
+                writeString((String) value);
+                continue;
+            }
+
+            if (valueClass == Integer.class) {
+                writeInt32((Integer) value);
+                continue;
+            }
+
+            if (valueClass == Long.class) {
+                writeInt64((Long) value);
+                continue;
+            }
+
+            if (valueClass == Boolean.class) {
+                writeBool((Boolean) value);
+                continue;
+            }
+
+            if (valueClass == BigDecimal.class) {
+                writeDecimal((BigDecimal) value, 0, null);
+                continue;
+            }
+
+            if (valueClass == JSONArray.class) {
+                write((JSONArray) value);
+                continue;
+            }
+
+            if (valueClass == JSONObject.class) {
+                write((JSONObject) value);
+                continue;
+            }
+
+            ObjectWriter objectWriter = context.getObjectWriter(valueClass, valueClass);
+            objectWriter.write(this, value, null, null, 0);
+        }
+
+        endObject();
+    }
 
     public void writeAny(Object value) {
         if (value == null) {
@@ -2213,7 +2316,19 @@ public abstract class JSONWriter
          * SortedMap and derived classes do not need to do this.
          * @since 2.0.48
          */
-        SortMapEntriesByKeys(1L << 41);
+        SortMapEntriesByKeys(1L << 41),
+
+        /**
+         * JSON formatting support using 4 spaces for indentation
+         * @since 2.0.54
+         */
+        PrettyFormatWith2Space(1L << 42),
+
+        /**
+         * JSON formatting support using 4 spaces for indentation
+         * @since 2.0.54
+         */
+        PrettyFormatWith4Space(1L << 43);
 
         public final long mask;
 
@@ -2583,23 +2698,23 @@ public abstract class JSONWriter
      * @deprecated
      */
     public final void incrementIndent() {
-        indent++;
+        level++;
     }
 
     /**
      * @deprecated
      */
     public final void decrementIdent() {
-        indent--;
+        level--;
     }
 
     /**
      * @deprecated
      */
     public void println() {
-        writeChar('\n');
-        for (int i = 0; i < indent; ++i) {
-            writeChar('\t');
+        writeRaw('\n');
+        for (int i = 0; i < level; ++i) {
+            writeRaw('\t');
         }
     }
 
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
index b850c217e..c4db8c4bc 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
@@ -126,7 +126,7 @@ class JSONWriterUTF16
         startObject = true;
 
         int off = this.off;
-        int minCapacity = off + (pretty ? 3 + indent : 1);
+        int minCapacity = off + 3 + pretty * level;
         if (minCapacity >= chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -134,12 +134,8 @@ class JSONWriterUTF16
         final char[] chars = this.chars;
         chars[off++] = (byte) '{';
 
-        if (pretty) {
-            indent++;
-            chars[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                chars[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(chars, off);
         }
         this.off = off;
     }
@@ -148,18 +144,14 @@ class JSONWriterUTF16
     public final void endObject() {
         level--;
         int off = this.off;
-        int minCapacity = off + (pretty ? 2 + indent : 1);
+        int minCapacity = off + 1 + (pretty == 0 ? 0 : pretty * level + 1);
         if (minCapacity >= chars.length) {
             ensureCapacity(minCapacity);
         }
 
         final char[] chars = this.chars;
-        if (pretty) {
-            indent--;
-            chars[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                chars[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(chars, off);
         }
 
         chars[off] = (byte) '}';
@@ -171,18 +163,15 @@ class JSONWriterUTF16
     public final void writeComma() {
         startObject = false;
         int off = this.off;
-        int minCapacity = off + (pretty ? 2 + indent : 1);
+        int minCapacity = off + 2 + pretty * level;
         if (minCapacity >= chars.length) {
             ensureCapacity(minCapacity);
         }
 
         final char[] chars = this.chars;
         chars[off++] = (byte) ',';
-        if (pretty) {
-            chars[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                chars[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(chars, off);
         }
         this.off = off;
     }
@@ -195,19 +184,15 @@ class JSONWriterUTF16
 
         level++;
         int off = this.off;
-        int minCapacity = off + (pretty ? 3 + indent : 1);
+        int minCapacity = off + 3 + pretty * level;
         if (minCapacity >= chars.length) {
             ensureCapacity(minCapacity);
         }
 
         final char[] chars = this.chars;
         chars[off++] = (byte) '[';
-        if (pretty) {
-            indent++;
-            chars[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                chars[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(chars, off);
         }
         this.off = off;
     }
@@ -216,18 +201,14 @@ class JSONWriterUTF16
     public final void endArray() {
         level--;
         int off = this.off;
-        int minCapacity = off + (pretty ? 2 + indent : 1);
+        int minCapacity = off + 1 + (pretty == 0 ? 0 : pretty * level + 1);
         if (minCapacity >= chars.length) {
             ensureCapacity(minCapacity);
         }
 
         final char[] chars = this.chars;
-        if (pretty) {
-            indent--;
-            chars[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                chars[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(chars, off);
         }
         chars[off] = (byte) ']';
         this.off = off + 1;
@@ -235,6 +216,10 @@ class JSONWriterUTF16
     }
 
     public final void writeString(List<String> list) {
+        if (pretty != PRETTY_NON) {
+            super.writeString(list);
+            return;
+        }
         // startArray();
         if (off == chars.length) {
             ensureCapacity(off + 1);
@@ -1542,7 +1527,7 @@ class JSONWriterUTF16
     @Override
     public final void writeNameRaw(char[] name) {
         int off = this.off;
-        int minCapacity = off + name.length + 2 + indent;
+        int minCapacity = off + name.length + 2 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1552,8 +1537,8 @@ class JSONWriterUTF16
         } else {
             final char[] chars = this.chars;
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
         System.arraycopy(name, 0, chars, off, name.length);
@@ -1563,7 +1548,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName2Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1573,8 +1558,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1585,7 +1570,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName3Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1595,8 +1580,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1607,7 +1592,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName4Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1617,8 +1602,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1629,7 +1614,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName5Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1639,8 +1624,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1651,7 +1636,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName6Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 11 + indent;
+        int minCapacity = off + 11 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1661,8 +1646,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1674,7 +1659,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName7Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 12 + indent;
+        int minCapacity = off + 12 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1684,8 +1669,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1698,7 +1683,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName8Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 13 + indent;
+        int minCapacity = off + 13 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1708,8 +1693,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1723,7 +1708,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName9Raw(long name0, int name1) {
         int off = this.off;
-        int minCapacity = off + 14 + indent;
+        int minCapacity = off + 14 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1733,8 +1718,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1745,7 +1730,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName10Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1755,8 +1740,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1767,7 +1752,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName11Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1777,8 +1762,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1789,7 +1774,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName12Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1799,8 +1784,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1811,7 +1796,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName13Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1821,8 +1806,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1833,7 +1818,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName14Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 19 + indent;
+        int minCapacity = off + 19 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1843,8 +1828,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1856,7 +1841,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName15Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 20 + indent;
+        int minCapacity = off + 20 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1866,8 +1851,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1880,7 +1865,7 @@ class JSONWriterUTF16
     @Override
     public final void writeName16Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 21 + indent;
+        int minCapacity = off + 21 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -1890,8 +1875,8 @@ class JSONWriterUTF16
             startObject = false;
         } else {
             chars[off++] = ',';
-            if (pretty) {
-                off = indent(chars, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(chars, off);
             }
         }
 
@@ -1962,18 +1947,16 @@ class JSONWriterUTF16
                         | ((name1 & 0xFF00_0000_0000_0000L) >> 8));
     }
 
-    private static int indent(char[] chars, int off, int indent) {
-        chars[off++] = '\n';
-        int end = off + indent;
-        while (off < end) {
-            chars[off++] = '\t';
-        }
-        return off;
+    private int indent(char[] chars, int off) {
+        chars[off] = '\n';
+        int toIndex = off + 1 + pretty * level;
+        Arrays.fill(chars, off + 1, toIndex, pretty == PRETTY_TAB ? '\t' : ' ');
+        return toIndex;
     }
 
     @Override
     public final void writeNameRaw(char[] chars, int off, int len) {
-        int minCapacity = this.off + len + 2 + indent;
+        int minCapacity = this.off + len + 2 + pretty * level;
         if (minCapacity >= this.chars.length) {
             ensureCapacity(minCapacity);
         }
@@ -2946,6 +2929,11 @@ class JSONWriterUTF16
 
     @Override
     public final void write(JSONObject map) {
+        if (pretty != PRETTY_NON) {
+            super.write(map);
+            return;
+        }
+
         if (map == null) {
             this.writeNull();
             return;
@@ -3039,10 +3027,7 @@ class JSONWriterUTF16
             objectWriter.write(this, value, null, null, 0);
         }
 
-        if (off == chars.length) {
-            ensureCapacity(off + 1);
-        }
-        chars[off++] = '}';
+        endObject();
     }
 
     @Override
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
index 8fa50d841..4e9bc3449 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
@@ -222,7 +222,7 @@ class JSONWriterUTF8
         startObject = true;
 
         int off = this.off;
-        int minCapacity = off + (pretty ? 3 + indent : 1);
+        int minCapacity = off + 3 + pretty * level;
         if (minCapacity >= bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -230,12 +230,8 @@ class JSONWriterUTF8
         final byte[] bytes = this.bytes;
         bytes[off++] = (byte) '{';
 
-        if (pretty) {
-            indent++;
-            bytes[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                bytes[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(bytes, off);
         }
         this.off = off;
     }
@@ -244,18 +240,14 @@ class JSONWriterUTF8
     public final void endObject() {
         level--;
         int off = this.off;
-        int minCapacity = off + (pretty ? 2 + indent : 1);
+        int minCapacity = off + 1 + (pretty == 0 ? 0 : pretty * level + 1);
         if (minCapacity >= bytes.length) {
             ensureCapacity(minCapacity);
         }
 
         final byte[] bytes = this.bytes;
-        if (pretty) {
-            indent--;
-            bytes[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                bytes[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(bytes, off);
         }
 
         bytes[off] = (byte) '}';
@@ -267,18 +259,15 @@ class JSONWriterUTF8
     public final void writeComma() {
         startObject = false;
         int off = this.off;
-        int minCapacity = off + (pretty ? 2 + indent : 1);
+        int minCapacity = off + 2 + pretty * level;
         if (minCapacity >= bytes.length) {
             ensureCapacity(minCapacity);
         }
 
         final byte[] bytes = this.bytes;
         bytes[off++] = ',';
-        if (pretty) {
-            bytes[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                bytes[off++] = '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(bytes, off);
         }
         this.off = off;
     }
@@ -291,19 +280,15 @@ class JSONWriterUTF8
 
         level++;
         int off = this.off;
-        int minCapacity = off + (pretty ? 3 + indent : 1);
+        int minCapacity = off + 3 + pretty * level;
         if (minCapacity >= bytes.length) {
             ensureCapacity(minCapacity);
         }
 
         final byte[] bytes = this.bytes;
         bytes[off++] = (byte) '[';
-        if (pretty) {
-            indent++;
-            bytes[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                bytes[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(bytes, off);
         }
         this.off = off;
     }
@@ -312,18 +297,14 @@ class JSONWriterUTF8
     public final void endArray() {
         level--;
         int off = this.off;
-        int minCapacity = off + (pretty ? 2 + indent : 1);
+        int minCapacity = off + 1 + (pretty == 0 ? 0 : pretty * level + 1);
         if (minCapacity >= bytes.length) {
             ensureCapacity(minCapacity);
         }
 
         final byte[] bytes = this.bytes;
-        if (pretty) {
-            indent--;
-            bytes[off++] = (byte) '\n';
-            for (int i = 0; i < indent; ++i) {
-                bytes[off++] = (byte) '\t';
-            }
+        if (pretty != PRETTY_NON) {
+            off = indent(bytes, off);
         }
         bytes[off] = (byte) ']';
         this.off = off + 1;
@@ -331,6 +312,10 @@ class JSONWriterUTF8
     }
 
     public final void writeString(List<String> list) {
+        if (pretty != PRETTY_NON) {
+            super.writeString(list);
+            return;
+        }
         // startArray();
         int off = this.off;
         if (off == bytes.length) {
@@ -1689,7 +1674,7 @@ class JSONWriterUTF8
     @Override
     public final void writeNameRaw(byte[] name) {
         int off = this.off;
-        int minCapacity = off + name.length + 2 + indent;
+        int minCapacity = off + name.length + 2 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1698,8 +1683,8 @@ class JSONWriterUTF8
         } else {
             final byte[] bytes = this.bytes;
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
         System.arraycopy(name, 0, bytes, off, name.length);
@@ -1709,7 +1694,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName2Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1719,8 +1704,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1728,19 +1713,17 @@ class JSONWriterUTF8
         this.off = off + 5;
     }
 
-    private static int indent(byte[] bytes, int off, int indent) {
-        bytes[off++] = '\n';
-        int end = off + indent;
-        while (off < end) {
-            bytes[off++] = '\t';
-        }
-        return off;
+    private int indent(byte[] bytes, int off) {
+        bytes[off] = '\n';
+        int toIndex = off + 1 + pretty * level;
+        Arrays.fill(bytes, off + 1, toIndex, pretty == PRETTY_TAB ? (byte) '\t' : (byte) ' ');
+        return toIndex;
     }
 
     @Override
     public final void writeName3Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1750,8 +1733,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1762,7 +1745,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName4Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1772,8 +1755,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1784,7 +1767,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName5Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 10 + indent;
+        int minCapacity = off + 10 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1794,8 +1777,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1806,7 +1789,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName6Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 11 + indent;
+        int minCapacity = off + 11 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1816,8 +1799,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1829,7 +1812,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName7Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 12 + indent;
+        int minCapacity = off + 12 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1839,8 +1822,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1853,7 +1836,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName8Raw(long name) {
         int off = this.off;
-        int minCapacity = off + 13 + indent;
+        int minCapacity = off + 13 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1863,8 +1846,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1878,7 +1861,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName9Raw(long name0, int name1) {
         int off = this.off;
-        int minCapacity = off + 14 + indent;
+        int minCapacity = off + 14 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1888,8 +1871,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1901,7 +1884,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName10Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1911,8 +1894,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1924,7 +1907,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName11Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1934,8 +1917,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1947,7 +1930,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName12Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1957,8 +1940,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1970,7 +1953,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName13Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 18 + indent;
+        int minCapacity = off + 18 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -1980,8 +1963,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -1993,7 +1976,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName14Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 19 + indent;
+        int minCapacity = off + 19 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -2003,8 +1986,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -2017,7 +2000,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName15Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 20 + indent;
+        int minCapacity = off + 20 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -2027,8 +2010,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -2042,7 +2025,7 @@ class JSONWriterUTF8
     @Override
     public final void writeName16Raw(long name0, long name1) {
         int off = this.off;
-        int minCapacity = off + 21 + indent;
+        int minCapacity = off + 21 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -2052,8 +2035,8 @@ class JSONWriterUTF8
             startObject = false;
         } else {
             bytes[off++] = ',';
-            if (pretty) {
-                off = indent(bytes, off, indent);
+            if (pretty != PRETTY_NON) {
+                off = indent(bytes, off);
             }
         }
 
@@ -2097,7 +2080,7 @@ class JSONWriterUTF8
 
     @Override
     public final void writeNameRaw(byte[] bytes, int off, int len) {
-        int minCapacity = this.off + len + 2 + indent;
+        int minCapacity = this.off + len + 2 + pretty * level;
         if (minCapacity >= this.bytes.length) {
             ensureCapacity(minCapacity);
         }
@@ -2994,6 +2977,11 @@ class JSONWriterUTF8
 
     @Override
     public final void write(JSONObject map) {
+        if (pretty != PRETTY_NON) {
+            super.write(map);
+            return;
+        }
+
         if (map == null) {
             this.writeNull();
             return;
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplList.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplList.java
index c1404fbaa..195b3e21b 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplList.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplList.java
@@ -267,8 +267,14 @@ final class ObjectWriterImplList
         JSONWriter.Context context = jsonWriter.context;
         ObjectWriterProvider provider = context.provider;
 
+        int size = list.size();
+        if (size == 0) {
+            jsonWriter.writeRaw('[', ']');
+            return;
+        }
+
         jsonWriter.startArray();
-        for (int i = 0; i < list.size(); i++) {
+        for (int i = 0; i < size; i++) {
             if (i != 0) {
                 jsonWriter.writeComma();
             }
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java
index 9c965e675..fc76b0e91 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java
@@ -404,19 +404,21 @@ public final class ObjectWriterImplMap
             writeWithFilter(jsonWriter, object, fieldName, fieldType, features);
             return;
         }
+        Map map = (Map) object;
 
         boolean refDetect = jsonWriter.isRefDetect();
+        boolean writeTypeInfo = (fieldType == this.objectType && jsonWriter.isWriteMapTypeInfo(object, objectClass, features))
+                || jsonWriter.isWriteTypeInfo(object, fieldType, features);
+        if (!writeTypeInfo && map.isEmpty()) {
+            jsonWriter.writeRaw('{', '}');
+            return;
+        }
 
         jsonWriter.startObject();
-
-        if ((fieldType == this.objectType && jsonWriter.isWriteMapTypeInfo(object, objectClass, features))
-                || jsonWriter.isWriteTypeInfo(object, fieldType, features)
-        ) {
+        if (writeTypeInfo) {
             writeTypeInfo(jsonWriter);
         }
 
-        Map map = (Map) object;
-
         features |= jsonWriter.getFeatures();
         if ((features & (MapSortField.mask | SortMapEntriesByKeys.mask)) != 0) {
             if (!(map instanceof SortedMap)


Test patch:
diff --git a/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java b/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java
index 221e4d9c9..7e998b4a3 100644
--- a/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/JSONWriterTest.java
@@ -1010,6 +1010,17 @@ public class JSONWriterTest {
         }
     }
 
+    @Test
+    public void writeObjectNonStringKey() {
+        JSONObject object = new JSONObject();
+        ((Map) object).put(123, "123");
+        JSONWriter jsonWriter = JSONWriter.ofUTF8(PrettyFormat);
+        jsonWriter.write(object);
+        assertEquals("{\n" +
+                "\t123:\"123\"\n" +
+                "}", jsonWriter.toString());
+    }
+
     @Test
     public void writeString1() {
         char[] chars = "01234567890".toCharArray();
@@ -2863,4 +2874,33 @@ public class JSONWriterTest {
         context.setFeatures(features);
         assertEquals(features, context.getFeatures());
     }
+
+    @Test
+    public void writeNull() {
+        {
+            JSONWriter jsonWriter = JSONWriter.of();
+            jsonWriter.write((JSONObject) null);
+            assertEquals("null", jsonWriter.toString());
+        }
+        {
+            JSONWriter jsonWriter = JSONWriter.ofUTF8();
+            jsonWriter.write((JSONObject) null);
+            assertEquals("null", jsonWriter.toString());
+        }
+        {
+            JSONWriter jsonWriter = JSONWriter.ofUTF16();
+            jsonWriter.write((JSONObject) null);
+            assertEquals("null", jsonWriter.toString());
+        }
+        {
+            JSONWriter jsonWriter = JSONWriter.ofPretty();
+            jsonWriter.write((JSONObject) null);
+            assertEquals("null", jsonWriter.toString());
+        }
+        {
+            JSONWriter jsonWriter = JSONWriter.ofPretty();
+            jsonWriter.write((Map) null);
+            assertEquals("null", jsonWriter.toString());
+        }
+    }
 }
diff --git a/core/src/test/java/com/alibaba/fastjson2/JSONWriterUTF16Test.java b/core/src/test/java/com/alibaba/fastjson2/JSONWriterUTF16Test.java
index d9d8f8216..389776a07 100644
--- a/core/src/test/java/com/alibaba/fastjson2/JSONWriterUTF16Test.java
+++ b/core/src/test/java/com/alibaba/fastjson2/JSONWriterUTF16Test.java
@@ -644,7 +644,7 @@ public class JSONWriterUTF16Test {
     @Test
     public void writeName8Pretty() {
         JSONWriterUTF16 jsonWriter = new JSONWriterUTF16(JSONFactory.createWriteContext(PrettyFormat));
-        jsonWriter.indent += 2;
+        jsonWriter.level += 2;
 
         char[] name = "a123".toCharArray();
         long nameValue = UNSAFE.getLong(name, ARRAY_CHAR_BASE_OFFSET);
@@ -712,13 +712,14 @@ public class JSONWriterUTF16Test {
 
         char[] name = "a123".toCharArray();
         long nameValue = UNSAFE.getLong(name, ARRAY_CHAR_BASE_OFFSET);
+        byte PRETTY_NON = 0, PRETTY_TAB = 1, PRETTY_SPACE = 3;
 
         final int initOffset = 8183;
         for (int i = 0; i < 16; i++) {
             int initSize = 8196 - i;
             {
                 jsonWriter.startObject = true;
-                jsonWriter.pretty = false;
+                jsonWriter.pretty = PRETTY_NON;
 
                 jsonWriter.chars = new char[initSize];
                 jsonWriter.off = initOffset;
@@ -726,7 +727,7 @@ public class JSONWriterUTF16Test {
             }
             {
                 jsonWriter.startObject = false;
-                jsonWriter.pretty = false;
+                jsonWriter.pretty = PRETTY_NON;
 
                 jsonWriter.chars = new char[initSize];
                 jsonWriter.off = initOffset;
@@ -734,7 +735,7 @@ public class JSONWriterUTF16Test {
             }
             {
                 jsonWriter.startObject = false;
-                jsonWriter.pretty = true;
+                jsonWriter.pretty = PRETTY_TAB;
 
                 jsonWriter.chars = new char[initSize];
                 jsonWriter.off = initOffset;
diff --git a/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java b/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java
new file mode 100644
index 000000000..a8d016aef
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java
@@ -0,0 +1,313 @@
+package com.alibaba.fastjson2.features;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.fastjson2.JSONWriter;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.alibaba.fastjson2.JSONWriter.Feature.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PrettyFormatTest {
+    @Test
+    public void test() {
+        assertEquals(
+                "{\n" +
+                        "\t\"id\":123,\n" +
+                        "\t\"value\":\"abc\"\n" +
+                        "}",
+                JSONObject.of("id", 123, "value", "abc")
+                        .toString(PrettyFormat));
+        assertEquals(
+                "{\n" +
+                        "\t\"id\":123,\n" +
+                        "\t\"value\":\"abc\"\n" +
+                        "}",
+                JSONObject.of("id", 123, "value", "abc")
+                        .toString(PrettyFormat, OptimizedForAscii));
+        assertEquals(
+                "{\n" +
+                        "    \"id\":123,\n" +
+                        "    \"value\":\"abc\"\n" +
+                        "}",
+                JSONObject.of("id", 123, "value", "abc")
+                        .toString(PrettyFormatWith4Space));
+        assertEquals(
+                "{\n" +
+                        "    \"id\":123,\n" +
+                        "    \"value\":\"abc\"\n" +
+                        "}",
+                JSONObject.of("id", 123, "value", "abc")
+                        .toString(PrettyFormatWith4Space, OptimizedForAscii));
+
+        assertEquals(
+                "{\n" +
+                        "  \"id\":123,\n" +
+                        "  \"value\":\"abc\"\n" +
+                        "}",
+                JSONObject.of("id", 123, "value", "abc")
+                        .toString(PrettyFormatWith2Space));
+        assertEquals(
+                "{\n" +
+                        "  \"id\":123,\n" +
+                        "  \"value\":\"abc\"\n" +
+                        "}",
+                JSONObject.of("id", 123, "value", "abc")
+                        .toString(PrettyFormatWith2Space, OptimizedForAscii));
+    }
+
+    @Test
+    public void test1() {
+        Bean1 bean = new Bean1();
+        bean.setId(123);
+        bean.setValue("abc");
+
+        assertEquals(
+                "{\n" +
+                        "\t\"id\":123,\n" +
+                        "\t\"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormat));
+        assertEquals(
+                "{\n" +
+                        "\t\"id\":123,\n" +
+                        "\t\"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormat, OptimizedForAscii));
+        assertEquals(
+                "{\n" +
+                        "    \"id\":123,\n" +
+                        "    \"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormatWith4Space));
+        assertEquals(
+                "{\n" +
+                        "    \"id\":123,\n" +
+                        "    \"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormatWith4Space, OptimizedForAscii));
+
+        Bean1[] array = new Bean1[] {bean};
+        assertEquals(
+                "[\n" +
+                        "\t{\n" +
+                        "\t\t\"id\":123,\n" +
+                        "\t\t\"value\":\"abc\"\n" +
+                        "\t}\n" +
+                        "]",
+                JSON.toJSONString(array, PrettyFormat));
+        assertEquals(
+                "[\n" +
+                        "    {\n" +
+                        "        \"id\":123,\n" +
+                        "        \"value\":\"abc\"\n" +
+                        "    }\n" +
+                        "]",
+                JSON.toJSONString(array, PrettyFormatWith4Space));
+    }
+
+    @Data
+    private static class Bean1 {
+        private int id;
+        private String value;
+    }
+
+    @Test
+    public void test2() {
+        Bean2 bean = new Bean2();
+        bean.setId(123);
+        bean.setValue("abc");
+
+        assertEquals(
+                "{\n" +
+                        "\t\"id\":123,\n" +
+                        "\t\"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormat));
+        assertEquals(
+                "{\n" +
+                        "\t\"id\":123,\n" +
+                        "\t\"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormat, OptimizedForAscii));
+        assertEquals(
+                "{\n" +
+                        "    \"id\":123,\n" +
+                        "    \"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormatWith4Space));
+        assertEquals(
+                "{\n" +
+                        "    \"id\":123,\n" +
+                        "    \"value\":\"abc\"\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormatWith4Space, OptimizedForAscii));
+
+        Bean2[] array = new Bean2[] {bean};
+        assertEquals(
+                "[\n" +
+                        "\t{\n" +
+                        "\t\t\"id\":123,\n" +
+                        "\t\t\"value\":\"abc\"\n" +
+                        "\t}\n" +
+                        "]",
+                JSON.toJSONString(array, PrettyFormat));
+        assertEquals(
+                "[\n" +
+                        "    {\n" +
+                        "        \"id\":123,\n" +
+                        "        \"value\":\"abc\"\n" +
+                        "    }\n" +
+                        "]",
+                JSON.toJSONString(array, PrettyFormatWith4Space));
+    }
+
+    @Data
+    public static class Bean2 {
+        private int id;
+        private String value;
+    }
+
+    @Test
+    public void test3() {
+        Bean3 bean = new Bean3();
+        bean.values.add("a01");
+        bean.values.add("a02");
+
+        assertEquals(
+                "{\n" +
+                        "\t\"values\":[\n" +
+                        "\t\t\"a01\",\n" +
+                        "\t\t\"a02\"\n" +
+                        "\t]\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormat));
+        assertEquals(
+                "{\n" +
+                        "\t\"values\":[\n" +
+                        "\t\t\"a01\",\n" +
+                        "\t\t\"a02\"\n" +
+                        "\t]\n" +
+                        "}",
+                JSON.toJSONString(bean, PrettyFormat, OptimizedForAscii));
+    }
+
+    public static class Bean3 {
+        public List<String> values = new ArrayList<>();
+    }
+
+    @Test
+    public void ofPrettyUTF16() {
+        JSONWriter jsonWriter = JSONWriter.ofUTF16();
+        jsonWriter = JSONWriter.ofPretty(jsonWriter);
+        jsonWriter = JSONWriter.ofPretty(jsonWriter);
+        jsonWriter.startObject();
+        jsonWriter.writeNameValue("id", 123);
+        jsonWriter.endObject();
+        jsonWriter.close();
+
+        assertEquals("{\n" +
+                "\t\"id\"123\n" +
+                "}", jsonWriter.toString());
+
+        jsonWriter.incrementIndent();
+        assertEquals(1, jsonWriter.level());
+        jsonWriter.println();
+        assertEquals("{\n" +
+                "\t\"id\"123\n" +
+                "}\n\t", jsonWriter.toString());
+        jsonWriter.decrementIdent();
+        assertEquals(0, jsonWriter.level());
+    }
+
+    @Test
+    public void ofPrettyUTF8() {
+        JSONWriter jsonWriter = JSONWriter.ofUTF8();
+        jsonWriter = JSONWriter.ofPretty(jsonWriter);
+        jsonWriter = JSONWriter.ofPretty(jsonWriter);
+        jsonWriter.startObject();
+        jsonWriter.writeNameValue("id", 123);
+        jsonWriter.endObject();
+        jsonWriter.close();
+
+        assertEquals("{\n" +
+                "\t\"id\"123\n" +
+                "}", jsonWriter.toString());
+
+        jsonWriter.incrementIndent();
+        assertEquals(1, jsonWriter.level());
+        jsonWriter.println();
+        assertEquals("{\n" +
+                "\t\"id\"123\n" +
+                "}\n\t", jsonWriter.toString());
+        jsonWriter.decrementIdent();
+        assertEquals(0, jsonWriter.level());
+    }
+
+    @Test
+    public void testJSONObject() {
+        {
+            JSONObject object = JSONObject.of(
+                    "f0", null,
+                    "f1", 101,
+                    "f2", 102L,
+                    "f3", new BigDecimal("103"),
+                    "f4", new JSONObject(),
+                    "f5", new JSONArray(),
+                    "f6", (short) 106,
+                    "f7", true
+            );
+            {
+                JSONWriter jsonWriter = JSONWriter.ofUTF16(PrettyFormat);
+                jsonWriter.write(object);
+                assertEquals("{\n" +
+                        "\t\"f1\":101,\n" +
+                        "\t\"f2\":102,\n" +
+                        "\t\"f3\":103,\n" +
+                        "\t\"f4\":{},\n" +
+                        "\t\"f5\":[],\n" +
+                        "\t\"f6\":106,\n" +
+                        "\t\"f7\":true\n" +
+                        "}", jsonWriter.toString());
+            }
+            {
+                JSONWriter jsonWriter = JSONWriter.ofUTF8(PrettyFormat);
+                jsonWriter.write(object);
+                assertEquals("{\n" +
+                        "\t\"f1\":101,\n" +
+                        "\t\"f2\":102,\n" +
+                        "\t\"f3\":103,\n" +
+                        "\t\"f4\":{},\n" +
+                        "\t\"f5\":[],\n" +
+                        "\t\"f6\":106,\n" +
+                        "\t\"f7\":true\n" +
+                        "}", jsonWriter.toString());
+            }
+            {
+                JSONWriter jsonWriter = JSONWriter.of(PrettyFormat, WriteNulls);
+                jsonWriter.write(object);
+                assertEquals("{\n" +
+                        "\t\"f0\":null,\n" +
+                        "\t\"f1\":101,\n" +
+                        "\t\"f2\":102,\n" +
+                        "\t\"f3\":103,\n" +
+                        "\t\"f4\":{},\n" +
+                        "\t\"f5\":[],\n" +
+                        "\t\"f6\":106,\n" +
+                        "\t\"f7\":true\n" +
+                        "}", jsonWriter.toString());
+            }
+        }
+        {
+            JSONWriter jsonWriter = JSONWriter.of(PrettyFormat);
+            jsonWriter.write(JSONObject.of());
+            assertEquals("{}", jsonWriter.toString());
+        }
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1591.java b/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1591.java
index a8e11e3f8..e46b4e3a4 100644
--- a/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1591.java
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_1500/Issue1591.java
@@ -25,9 +25,16 @@ public class Issue1591 {
             );
         }
 
-        String str = JSON.toJSONString(array, JSONWriter.Feature.PrettyFormat);
-        assertNotNull(str);
-        assertEquals(array, JSON.parseArray(str));
+        {
+            String str = JSON.toJSONString(array, JSONWriter.Feature.PrettyFormat);
+            assertNotNull(str);
+            assertEquals(array, JSON.parseArray(str));
+        }
+        {
+            String str = JSON.toJSONString(array, JSONWriter.Feature.PrettyFormat, JSONWriter.Feature.OptimizedForAscii);
+            assertNotNull(str);
+            assertEquals(array, JSON.parseArray(str));
+        }
     }
 
     @Test
@@ -49,8 +56,15 @@ public class Issue1591 {
             );
         }
 
-        String str = JSON.toJSONString(array, JSONWriter.Feature.PrettyFormat);
-        assertNotNull(str);
-        assertEquals(array, JSON.parseArray(str));
+        {
+            String str = JSON.toJSONString(array, JSONWriter.Feature.PrettyFormat);
+            assertNotNull(str);
+            assertEquals(array, JSON.parseArray(str));
+        }
+        {
+            String str = JSON.toJSONString(array, JSONWriter.Feature.PrettyFormat, JSONWriter.Feature.OptimizedForAscii);
+            assertNotNull(str);
+            assertEquals(array, JSON.parseArray(str));
+        }
     }
 }
diff --git a/core/src/test/java/com/alibaba/fastjson2/write/PrettyTest.java b/core/src/test/java/com/alibaba/fastjson2/write/PrettyTest.java
index e47e9f00a..462a7e46b 100644
--- a/core/src/test/java/com/alibaba/fastjson2/write/PrettyTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/write/PrettyTest.java
@@ -182,9 +182,7 @@ public class PrettyTest {
         JSONWriter jw = JSONWriter.ofPretty();
         jw.writeAny(Collections.emptyMap());
 
-        assertEquals("{\n" +
-                        "\t\n" +
-                        "}",
+        assertEquals("{}",
                 jw.toString());
     }
 
@@ -201,12 +199,8 @@ public class PrettyTest {
         assertEquals("{\n" +
                         "\t\"id\":1001,\n" +
                         "\t\"name\":\"DataWorks\",\n" +
-                        "\t\"array\":[\n" +
-                        "\t\t\n" +
-                        "\t],\n" +
-                        "\t\"object\":{\n" +
-                        "\t\t\n" +
-                        "\t}\n" +
+                        "\t\"array\":[],\n" +
+                        "\t\"object\":{}\n" +
                         "}",
                 jw.toString());
     }
@@ -215,7 +209,7 @@ public class PrettyTest {
     public void test_pretty_array() {
         JSONWriter jw = JSONWriter.ofPretty();
         jw.writeAny(Collections.emptyList());
-        assertEquals("[\n\t\n]", jw.toString());
+        assertEquals("[]", jw.toString());
     }
 
     @Test