Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
0658c3a
feat(java): support JSON bean properties
chaokunyang Jul 1, 2026
4e2e154
perf(java): optimize Fory JSON UTF8 reads
chaokunyang Jul 1, 2026
ca8dbea
perf(java): specialize JSON string arrays
chaokunyang Jul 1, 2026
06a2b66
perf(java): speed up Fory JSON long writes
chaokunyang Jul 1, 2026
4416e3e
perf(java): write JSON scalar dates directly
chaokunyang Jul 1, 2026
b59753d
feat(java): add Fory JSON OutputStream writes
chaokunyang Jul 1, 2026
7a0c0b2
perf(java): specialize Fory JSON generated scalars
chaokunyang Jul 1, 2026
515c7ff
perf(java): optimize Fory JSON string collections
chaokunyang Jul 1, 2026
792b35a
perf(java): specialize Fory JSON BigDecimal writes
chaokunyang Jul 1, 2026
19f6470
perf(java): speed up Fory JSON BigDecimal reads
chaokunyang Jul 1, 2026
821883a
perf(java): fast-path UTC JSON timestamps
chaokunyang Jul 1, 2026
83baa28
perf(java): split Fory JSON generated writers
chaokunyang Jul 1, 2026
5982976
perf(java): add Fory JSON common array codecs
chaokunyang Jul 1, 2026
4643cf4
perf(java): split Fory JSON generated readers
chaokunyang Jul 1, 2026
8d7492c
perf(java): pack Fory JSON generated field prefixes
chaokunyang Jul 1, 2026
15500ab
perf(java): specialize Fory JSON ArrayList object writes
chaokunyang Jul 1, 2026
091cc9d
perf(java): copy Fory JSON numeric bytes directly
chaokunyang Jul 1, 2026
50f563d
perf(java): copy Fory JSON short Latin1 tails
chaokunyang Jul 1, 2026
78a0c19
perf(java): retain medium Fory JSON writer buffers
chaokunyang Jul 1, 2026
aab2406
perf(java): emit exact Fory JSON array writes
chaokunyang Jul 1, 2026
c7a12fc
perf(json): add short latin1 string writer path
chaokunyang Jul 1, 2026
6908a27
perf(json): split latin1 string writer dispatch
chaokunyang Jul 1, 2026
ece77e7
perf(json): match long ascii field tokens
chaokunyang Jul 1, 2026
139acbd
perf(json): read utf8 doubles directly
chaokunyang Jul 1, 2026
5f80129
perf(json): read small utf8 string arrays directly
chaokunyang Jul 1, 2026
397c7c7
perf(json): read utf8 uuids directly
chaokunyang Jul 1, 2026
ecdc09f
perf(json): read small utf8 long arrays directly
chaokunyang Jul 1, 2026
c35b43f
perf(json): read small utf8 arraylists exactly
chaokunyang Jul 2, 2026
8de2f1b
perf(json): read nine utf8 strings exactly
chaokunyang Jul 2, 2026
83b7f69
perf(json): split utf8 arraylist reads
chaokunyang Jul 2, 2026
f5263b8
perf(json): speed up negative long reads
chaokunyang Jul 2, 2026
4a69d7a
perf(json): avoid long tail division
chaokunyang Jul 2, 2026
cfa218b
perf(json): cache object array reads
chaokunyang Jul 2, 2026
55021c2
perf(json): stack object array scratch buffers
chaokunyang Jul 2, 2026
7673bd7
perf(json): read object arraylists exactly
chaokunyang Jul 2, 2026
6ea110d
perf(java): compact Fory JSON reader hot paths
chaokunyang Jul 2, 2026
3a62ded
perf(java): parse UTF-8 long digit blocks
chaokunyang Jul 2, 2026
63d006f
perf(java): compact Fory JSON UTF-8 string scan
chaokunyang Jul 2, 2026
0edc61d
perf(java): fold Fory JSON UTF-8 string mask
chaokunyang Jul 2, 2026
8295a5c
perf(java): split Fory JSON string tail scan
chaokunyang Jul 2, 2026
5302de8
fix(java): handle Fory JSON Latin-1 string boundary
chaokunyang Jul 2, 2026
ce5c01d
perf(java): elide Fory JSON string coder reads
chaokunyang Jul 2, 2026
86142f8
perf(java): tune Fory JSON writer split size
chaokunyang Jul 2, 2026
114d9e5
perf(java): write Fory JSON doubles directly
chaokunyang Jul 3, 2026
2be8a24
perf(java): combine Fory JSON Latin-1 ASCII checks
chaokunyang Jul 3, 2026
28dcdaf
Revert "perf(java): combine Fory JSON Latin-1 ASCII checks"
chaokunyang Jul 3, 2026
abd2e28
perf(java): pack Fory JSON dynamic field prefixes
chaokunyang Jul 3, 2026
ca44b51
perf(json): generate codecs in user package
chaokunyang Jul 3, 2026
d155388
perf(java): remove unused json generated metadata
chaokunyang Jul 3, 2026
a26f95f
perf(java): bulk copy long JSON strings
chaokunyang Jul 3, 2026
5c0152d
perf(java): split JSON string element comma path
chaokunyang Jul 3, 2026
ef949cb
perf(java): unroll JSON exact array writes
chaokunyang Jul 3, 2026
73221a1
perf(java): inline JSON reader group checks
chaokunyang Jul 4, 2026
97a8a70
perf(java): inline JSON reader codec values
chaokunyang Jul 4, 2026
5bdd7c4
perf(java): trim Fory JSON UTF8 whitespace checks
chaokunyang Jul 4, 2026
deb766d
perf(java): widen Fory JSON UTF8 string scan
chaokunyang Jul 4, 2026
eb753a6
perf(java): optimize Fory JSON short string writes
chaokunyang Jul 4, 2026
41933fc
perf(java): split JSON double token parser
chaokunyang Jul 4, 2026
389b760
perf(java): split JSON depth overflow path
chaokunyang Jul 4, 2026
197c247
perf(java): split JSON comma fast path
chaokunyang Jul 4, 2026
73bc753
perf(json): split medium Latin1 string writes
chaokunyang Jul 4, 2026
3f9a8b5
perf(json): clear only used pooled reader
chaokunyang Jul 5, 2026
ee6b6f1
perf(json): parse compact doubles two digits at a time
chaokunyang Jul 5, 2026
03389d7
perf(json): split short latin1 string tail
chaokunyang Jul 5, 2026
b843489
perf(json): fast-path ascii escape checks
chaokunyang Jul 5, 2026
8fa8d59
perf(json): use exact tails for short latin1 writes
chaokunyang Jul 5, 2026
64ea55e
perf(json): streamline UTF8 reads
chaokunyang Jul 5, 2026
0576824
perf(json): inline exact string array construction
chaokunyang Jul 5, 2026
a348a2a
perf(java): speed up Fory JSON Latin1 scalar reads
chaokunyang Jul 5, 2026
99e9d19
perf(java): speed up Fory JSON Latin1 string scanning
chaokunyang Jul 5, 2026
49e8b08
perf(java): shrink Latin1 JSON string reader
chaokunyang Jul 5, 2026
d068eda
perf(java): route ASCII JSON strings through UTF-8 reader
chaokunyang Jul 5, 2026
5c85b90
fix(java): keep JSON string input on string readers
chaokunyang Jul 5, 2026
a80791b
perf(java): add exact JSON string array reads
chaokunyang Jul 5, 2026
62b862e
perf(java): streamline JSON Latin1 string reads
chaokunyang Jul 5, 2026
4d28c14
perf(java): tune JSON reader group splitting
chaokunyang Jul 5, 2026
e9814e4
perf(java): use Latin1 JSON double token reads
chaokunyang Jul 5, 2026
7e0ee96
perf(java): parse Latin1 JSON longs by blocks
chaokunyang Jul 5, 2026
6c8a110
perf(java): speed up latin1 json string reads
chaokunyang Jul 5, 2026
72dd8d1
perf(java): optimize JSON string collection reads
chaokunyang Jul 5, 2026
5e32ec7
fix(java): fix Fory JSON CI failures
chaokunyang Jul 6, 2026
1ba1ed7
perf(java): optimize Fory JSON UTF16 temporal reads
chaokunyang Jul 6, 2026
c9b60ed
perf(java): speed up JSON string writer after UTF16 upgrade
chaokunyang Jul 6, 2026
6be89ed
perf(java): speed up JSON UTF16 field probes
chaokunyang Jul 6, 2026
a54efd4
perf(java): scan JSON UTF16 strings by word
chaokunyang Jul 6, 2026
86d066b
perf(java): speed up JSON UTF16 string writes
chaokunyang Jul 6, 2026
aac2e54
perf(java): inline JSON UTF16 string scan
chaokunyang Jul 6, 2026
3fb712a
perf(java): compact JSON ASCII word predicate
chaokunyang Jul 6, 2026
902bc23
perf(java): speed up JSON UTF16 field prefixes
chaokunyang Jul 6, 2026
e8686ec
perf(java): speed up JSON string list writes
chaokunyang Jul 6, 2026
120f1b5
perf(java): speed up JSON UTF16 numeric writes
chaokunyang Jul 6, 2026
0a93d0a
perf(java): speed up JSON UTF16 string transition
chaokunyang Jul 6, 2026
e1a0295
perf(java): cache JSON UTF16 enum field values
chaokunyang Jul 6, 2026
ce6b220
perf(java): split medium JSON readers
chaokunyang Jul 6, 2026
4325e16
perf(java): speed up UTF16 JSON string reads
chaokunyang Jul 6, 2026
f32554b
perf(java): use UTF16 JSON token reads in codegen
chaokunyang Jul 6, 2026
6ff2ba7
perf(java): skip UTF16 string quote reload
chaokunyang Jul 6, 2026
1e26770
perf(java): split large JSON string writers
chaokunyang Jul 6, 2026
bd62c0d
perf(java): predict JSON string writer coder
chaokunyang Jul 6, 2026
111b2a7
perf(java): speed up JSON UTF16 token reads
chaokunyang Jul 6, 2026
e35bf8b
perf(java): cache JSON UTF16 digit quads
chaokunyang Jul 6, 2026
32c64c4
perf(java): avoid JSON string coder reloads
chaokunyang Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,7 @@ public String toString() {
}
}

class NewArray extends AbstractExpression {
class NewArray extends Inlineable {
private TypeRef<?> type;
private Expression[] elements;

Expand Down Expand Up @@ -1662,59 +1662,35 @@ public ExprCode doGenCode(CodegenContext ctx) {
Class<?> rawType = getRawType(type);
String arrayType = getArrayType(rawType);
String value = ctx.newName("arr");
String arrayValue;
if (dims != null) {
// multi-dimension array
ExprCode dimsExprCode = dims.genCode(ctx);
if (StringUtils.isNotBlank(dimsExprCode.code())) {
codeBuilder.append(dimsExprCode.code()).append('\n');
}
// "${arrType} ${value} = new ${elementType}[$?][$?]...
codeBuilder
.append(arrayType)
.append(' ')
.append(value)
.append(" = new ")
.append(ctx.type(elemType));
// "new ${elementType}[$?][$?]..."
StringBuilder arrayValueBuilder = new StringBuilder("new ").append(ctx.type(elemType));
for (int i = 0; i < numDimensions; i++) {
// dims is dimensions array, which store size of per dim.
String idim = StringUtils.format("${dims}[${i}]", "dims", dimsExprCode.value(), "i", i);
codeBuilder.append('[').append(idim).append("]");
arrayValueBuilder.append('[').append(idim).append("]");
}
codeBuilder.append(';');
arrayValue = arrayValueBuilder.toString();
} else if (dim != null) {
ExprCode dimExprCode = dim.genCode(ctx);
if (StringUtils.isNotBlank(dimExprCode.code())) {
codeBuilder.append(dimExprCode.code()).append('\n');
}
StringBuilder arrayValueBuilder = new StringBuilder("new ").append(ctx.type(elemType));
arrayValueBuilder.append('[').append(dimExprCode.value()).append(']');
if (numDimensions > 1) {
// multi-dimension array
// "${arrType} ${value} = new ${elementType}[$?][][][]...
codeBuilder
.append(arrayType)
.append(' ')
.append(value)
.append(" = new ")
.append(ctx.type(elemType));
codeBuilder.append('[').append(dimExprCode.value()).append(']');
for (int i = 1; i < numDimensions; i++) {
codeBuilder.append('[').append("]");
arrayValueBuilder.append('[').append("]");
}
codeBuilder.append(';');
} else {
// one-dimension array
String code =
StringUtils.format(
"${type} ${value} = new ${elemType}[${dim}];",
"type",
arrayType,
"elemType",
ctx.type(elemType),
"value",
value,
"dim",
dimExprCode.value());
codeBuilder.append(code);
}
arrayValue = arrayValueBuilder.toString();
} else {
// create array with init value
int len = elements.length;
Expand All @@ -1733,18 +1709,22 @@ public ExprCode doGenCode(CodegenContext ctx) {
}
}

String code =
arrayValue =
StringUtils.format(
"${type} ${value} = new ${type} {${args}};",
"type",
arrayType,
"value",
value,
"args",
argsBuilder.toString());
codeBuilder.append(code);
"new ${type} {${args}}", "type", arrayType, "args", argsBuilder.toString());
}

if (inlineCall) {
String code = StringUtils.isBlank(codeBuilder) ? null : codeBuilder.toString();
return new ExprCode(code, null, Code.variable(rawType, arrayValue));
}
codeBuilder
.append(arrayType)
.append(' ')
.append(value)
.append(" = ")
.append(arrayValue)
.append(';');
return new ExprCode(codeBuilder.toString(), null, Code.variable(rawType, value));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,38 @@ public static byte[] getStringBytes(String value) {
return (byte[]) getStringValue(value);
}

@Internal
public static void copyStringCharsToBytes(String value, byte[] target) {
int length = value.length();
if (length > (Integer.MAX_VALUE >>> 1)) {
throw new IllegalArgumentException("String is too large");
}
int numBytes = length << 1;
if (target.length < numBytes) {
throw new IllegalArgumentException("Target byte array is too small");
}
if (STRING_VALUE_FIELD_IS_CHARS) {
char[] chars = (char[]) getStringValue(value);
int offset = STRING_HAS_COUNT_OFFSET ? getStringOffset(value) : 0;
PlatformStringUtils.copyCharsToBytes(chars, offset, target, 0, numBytes);
return;
}
int byteIndex = 0;
if (NativeByteOrder.IS_LITTLE_ENDIAN) {
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
target[byteIndex++] = (byte) c;
target[byteIndex++] = (byte) (c >>> 8);
}
} else {
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
target[byteIndex++] = (byte) (c >>> 8);
target[byteIndex++] = (byte) c;
}
}
}

@Internal
public static byte getStringCoder(String value) {
return PlatformStringUtils.getStringCoder(value);
Expand Down Expand Up @@ -969,6 +1001,11 @@ public static String newLatin1StringZeroCopy(byte[] data) {
return newBytesStringZeroCopy(LATIN1, data);
}

@Internal
public static String newUtf16StringZeroCopy(byte[] data) {
return newBytesStringZeroCopy(UTF16, data);
}

private static String newBytesStringSlow(byte coder, byte[] data) {
if (coder == LATIN1) {
return new String(data, StandardCharsets.ISO_8859_1);
Expand Down
81 changes: 68 additions & 13 deletions java/fory-json/src/main/java/org/apache/fory/json/ForyJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@

package org.apache.fory.json;

import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.apache.fory.json.codec.GeneratedObjectCodec;
import org.apache.fory.json.reader.Latin1JsonReader;
import org.apache.fory.json.reader.Utf16JsonReader;
import org.apache.fory.json.reader.Utf8JsonReader;
import org.apache.fory.json.resolver.CodecRegistry;
import org.apache.fory.json.resolver.JsonSharedRegistry;
import org.apache.fory.json.resolver.JsonTypeInfo;
import org.apache.fory.json.resolver.JsonTypeResolver;
Expand All @@ -39,8 +40,10 @@
public final class ForyJson {
private static final int PREFERRED_SLOT_RETRIES = 2;
private static final int INITIAL_BUFFER_SIZE = 8192;
private static final int RETAINED_UTF16_BYTES = 64 * 1024;
private static final int PRIMARY_SLOT = -1;
private static final int TEMPORARY_SLOT = -2;
private static final byte[] EMPTY_BYTES = new byte[0];
private static final int DEFAULT_POOL_SIZE =
Math.max(1, Runtime.getRuntime().availableProcessors() * 4);

Expand All @@ -54,11 +57,10 @@ public final class ForyJson {
private final AtomicReference<PooledState> primarySlot;
private final AtomicReferenceArray<PooledState> slots;

ForyJson(
boolean writeNullFields, boolean codegenEnabled, int maxDepth, CodecRegistry codecRegistry) {
this.writeNullFields = writeNullFields;
this.maxDepth = maxDepth;
sharedRegistry = new JsonSharedRegistry(codegenEnabled, writeNullFields, codecRegistry);
ForyJson(JsonConfig config) {
this.writeNullFields = config.writeNullFields();
this.maxDepth = config.maxDepth();
sharedRegistry = new JsonSharedRegistry(config);
poolSize = DEFAULT_POOL_SIZE;
primarySlot =
new AtomicReference<>(
Expand Down Expand Up @@ -111,13 +113,34 @@ public byte[] toJsonBytes(Object value) {
}
}

/** Serializes {@code value} as UTF-8 JSON to {@code output}. */
public void writeJsonTo(Object value, OutputStream output) {
Objects.requireNonNull(output, "output");
PooledState entry = acquire();
JsonState state = entry.state;
Utf8JsonWriter writer = state.utf8Writer;
try {
if (value == null) {
writer.writeNull();
} else {
JsonTypeResolver resolver = state.typeResolver;
JsonTypeInfo typeInfo = state.rootTypeInfo(value.getClass());
typeInfo.codec().writeUtf8(writer, value, resolver);
}
writer.writeTo(output);
} finally {
writer.reset();
release(entry);
}
}

public <T> T fromJson(String json, Class<T> type) {
PooledState entry = acquire();
JsonState state = entry.state;
try {
return castValue(readJavaStringValue(json, type, type, state), type);
} finally {
state.clearReaders();
state.clearStringReaders();
release(entry);
}
}
Expand All @@ -130,7 +153,7 @@ public <T> T fromJson(String json, TypeRef<T> typeRef) {
Object value = readJavaStringValue(json, typeRef.getType(), typeRef.getRawType(), state);
return castValue(value, typeRef);
} finally {
state.clearReaders();
state.clearStringReaders();
release(entry);
}
}
Expand All @@ -141,7 +164,7 @@ public <T> T fromJson(byte[] bytes, Class<T> type) {
try {
return castValue(readUtf8Value(state.utf8Reader(bytes, maxDepth), type, type, state), type);
} finally {
state.clearReaders();
state.clearUtf8Reader();
release(entry);
}
}
Expand All @@ -156,7 +179,7 @@ public <T> T fromJson(byte[] bytes, TypeRef<T> typeRef) {
state.utf8Reader(bytes, maxDepth), typeRef.getType(), typeRef.getRawType(), state);
return castValue(value, typeRef);
} finally {
state.clearReaders();
state.clearUtf8Reader();
release(entry);
}
}
Expand Down Expand Up @@ -235,13 +258,15 @@ private Object readJavaStringValue(String json, Type type, Class<?> fallback, Js
if (StringSerializer.isBytesBackedString()) {
byte coder = StringSerializer.getStringCoder(json);
if (StringSerializer.isLatin1Coder(coder)) {
// Keep String input on its reader owner even when ASCII Latin1 bytes match UTF-8;
// custom JsonCodec implementations can observe readLatin1/readUtf8 dispatch.
return readLatin1Value(state.latin1Reader(json, maxDepth), type, fallback, state);
}
if (StringSerializer.isUtf16Coder(coder)) {
return readUtf16Value(state.utf16Reader(json, maxDepth), type, fallback, state);
}
}
return readUtf16Value(state.utf16Reader(json, maxDepth), type, fallback, state);
return readUtf16Value(state.legacyUtf16Reader(json, maxDepth), type, fallback, state);
}

private Object readLatin1Value(
Expand Down Expand Up @@ -299,6 +324,7 @@ private static final class JsonState {
private final Latin1JsonReader latin1Reader;
private final Utf16JsonReader utf16Reader;
private final JsonTypeResolver typeResolver;
private byte[] legacyUtf16Bytes;
private Type lastRootType;
private Class<?> lastRootFallback;
private JsonTypeInfo lastRootInfo;
Expand All @@ -310,6 +336,7 @@ private JsonState(boolean writeNullFields, JsonSharedRegistry sharedRegistry) {
latin1Reader = new Latin1JsonReader();
utf16Reader = new Utf16JsonReader();
typeResolver = new JsonTypeResolver(sharedRegistry);
legacyUtf16Bytes = EMPTY_BYTES;
}

private Latin1JsonReader latin1Reader(String input, int maxDepth) {
Expand All @@ -324,18 +351,46 @@ private Utf16JsonReader utf16Reader(String input, int maxDepth) {
return utf16Reader;
}

private Utf16JsonReader legacyUtf16Reader(String input, int maxDepth) {
int length = input.length();
if (length > (Integer.MAX_VALUE >>> 1)) {
throw new IllegalArgumentException("String is too large");
}
int numBytes = length << 1;
byte[] bytes;
if (numBytes <= RETAINED_UTF16_BYTES) {
bytes = legacyUtf16Bytes;
if (bytes.length < numBytes) {
bytes = new byte[Math.max(numBytes, INITIAL_BUFFER_SIZE)];
legacyUtf16Bytes = bytes;
}
} else {
bytes = new byte[numBytes];
}
// Legacy char[]-backed Strings are converted once so parsing still uses UTF16 byte loads.
StringSerializer.copyStringCharsToBytes(input, bytes);
utf16Reader.resetUtf16Bytes(input, bytes);
utf16Reader.resetDepth(maxDepth);
return utf16Reader;
}

private Utf8JsonReader utf8Reader(byte[] input, int maxDepth) {
utf8Reader.reset(input);
utf8Reader.resetDepth(maxDepth);
return utf8Reader;
}

private void clearReaders() {
// Clear only readers reset by the current public parse entry; clearing the unused readers shows
// up on small byte-input parses and does not release additional retained input.
private void clearStringReaders() {
latin1Reader.clearDepth();
utf16Reader.clearDepth();
utf8Reader.clearDepth();
latin1Reader.clear();
utf16Reader.clear();
}

private void clearUtf8Reader() {
utf8Reader.clearDepth();
utf8Reader.clear();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
public final class ForyJsonBuilder {
private boolean writeNullFields;
private boolean codegenEnabled = true;
private boolean propertyDiscoveryEnabled = true;
private int maxDepth = ForyJson.DEFAULT_MAX_DEPTH;
private final CodecRegistry codecRegistry = new CodecRegistry();

Expand All @@ -43,6 +44,12 @@ public ForyJsonBuilder withCodegen(boolean codegenEnabled) {
return this;
}

/** Enables JavaBean getter/setter JSON property discovery. */
public ForyJsonBuilder withPropertyDiscovery(boolean propertyDiscoveryEnabled) {
this.propertyDiscoveryEnabled = propertyDiscoveryEnabled;
return this;
}

/** Sets the maximum nested JSON object/array depth allowed while parsing. */
public ForyJsonBuilder maxDepth(int maxDepth) {
if (maxDepth < 1) {
Expand All @@ -59,6 +66,8 @@ public <T> ForyJsonBuilder registerCodec(Class<T> type, JsonCodec codec) {
}

public ForyJson build() {
return new ForyJson(writeNullFields, codegenEnabled, maxDepth, codecRegistry);
return new ForyJson(
new JsonConfig(
writeNullFields, codegenEnabled, propertyDiscoveryEnabled, maxDepth, codecRegistry));
}
}
Loading
Loading