跳到主要内容
版本:dev

Object Mapping

Thread safety, reuse, and code generation

ForyJson is immutable and thread-safe after build(). Reuse one instance instead of creating a builder and runtime for every operation. Registered and annotation-selected JsonValueCodec instances and the JsonTypeChecker may be called concurrently and must also be thread-safe.

Code generation and asynchronous compilation are enabled by default. Disabling code generation is useful for diagnostics or environments that prohibit runtime compilation:

ForyJson json =
ForyJson.builder()
.withCodegen(false)
.withAsyncCompilation(false)
.build();

withConcurrencyLevel sets the maximum number of root operations that execute concurrently. Additional callers wait until one of those fixed execution states is available. Root APIs on one ForyJson instance are not reentrant: a custom codec must continue through the concrete reader or writer passed to it instead of calling toJson, toJsonBytes, writeJsonTo, or fromJson on that instance.

Java object mapping

Default property discovery

By default, Fory JSON builds one logical property from members with the same Java property name:

  • eligible instance fields across the class hierarchy, including private, protected, package-private, and public fields;
  • public non-static JavaBean getters named getX();
  • public non-static boolean getters named isX();
  • public non-static void setters named setX(value).

Static, transient, synthetic, and Class<?> fields are excluded. getClass() and accessors whose value type is Class<?> are also excluded. An annotation placed on an ineligible member is rejected instead of being silently ignored.

An ordinary final field can be written but is not used as a mutable read sink. Use a record, JsonCreator, or a custom codec for immutable construction.

Field mode

Field mode disables getter and setter discovery while retaining eligible fields:

ForyJson json = ForyJson.builder().withFieldMode(true).build();

Annotations on methods are invalid in field mode because those methods are not part of the JSON property model.

Construction and input behavior

Fory JSON supports ordinary concrete classes, Java records, and classes with an explicit JsonCreator constructor or factory.

  • Records use their canonical constructor.
  • Creator-based classes use only the declared creator read schema and do not run setters afterward.
  • Unknown object members are skipped.
  • An ordinary class with a no-argument constructor runs that constructor before readable properties are assigned. Missing properties therefore retain values established by field initializers or that constructor.
  • On an ordinary JVM, a class without a no-argument constructor is allocated without running its constructors or field initializers. Its missing properties retain JVM zero or null values.
  • Creator reference parameters default to null and creator primitive parameters default to zero.
  • Duplicate ordinary properties use the last value. A polymorphic discriminator is stricter and must appear exactly once.
  • JSON null is rejected for primitive targets. Most reference targets return null, but a selected built-in or custom codec may define another result; for example, declared Optional targets return Optional.empty().

Android cannot construct an ordinary class without a usable no-argument constructor. GraalVM native image on JDK 25 and later also requires one for most ordinary classes; the supported exception is a Serializable class whose first non-serializable superclass is Object. For a portable construction contract, use a record, JsonCreator, or a no-argument constructor. Do not use ordinary-constructor side effects as a deserialization completion hook: when a no-argument constructor runs, property assignment happens afterward, and constructor-bypassing paths do not run it at all.

Supported Java types

The following groups have built-in mappings. Exact wire representations are stable JSON values, but application schemas should still declare the intended Java type when precision or construction matters.

GroupSupported types and behavior
Core scalarsboolean, numeric primitives, char, their boxed types, String, CharSequence, StringBuilder, StringBuffer
NumbersNumber, BigInteger, BigDecimal, Fory Float16 and BFloat16, AtomicInteger, AtomicLong
EnumsEnum constant names as JSON strings
ArraysPrimitive arrays, boxed arrays, String arrays, object arrays, and multidimensional arrays
CollectionsCollection, List, Set, Queue, deque, blocking, sorted, and navigable interfaces; their abstract bases; EnumSet; and concrete implementations with an accessible no-argument constructor
MapsMap, sorted, navigable, and concurrent interfaces; AbstractMap; EnumMap; and concrete implementations with an accessible no-argument constructor
Optional and atomicOptional, OptionalInt, OptionalLong, OptionalDouble, AtomicBoolean, AtomicReference, and atomic arrays
TimeDate, Calendar, TimeZone, LocalDate, LocalTime, LocalDateTime, Instant, Duration, ZoneOffset, ZoneId, ZonedDateTime, Year, YearMonth, MonthDay, Period, OffsetTime, OffsetDateTime, and supported chronology dates
Other JDK typesUUID, URI, File, Path, Locale, Charset, Currency, Pattern, BitSet, ByteBuffer
Optional modulesjava.sql.Date, Time, and Timestamp; Guava ImmutableList, ImmutableSet, ImmutableSortedSet, ImmutableMap, ImmutableBiMap, ImmutableSortedMap, and ImmutableIntArray when Guava is present
ObjectsMutable concrete classes, records, creator-based classes, JsonObject, and JsonArray

Collection interfaces are reconstructed with standard mutable implementations, such as ArrayList, LinkedHashSet, ArrayDeque, LinkedBlockingQueue, LinkedBlockingDeque, or TreeSet, according to the declared interface. Map interfaces similarly use LinkedHashMap, TreeMap, ConcurrentHashMap, or ConcurrentSkipListMap. ArrayBlockingQueue, Arrays.asList results, JDK immutable collections, empty/singleton/unmodifiable wrappers, constructor-constrained implementations, and unlisted Guava immutable implementations cannot be reconstructed. Guava support is optional and does not make Guava a required runtime dependency.

Non-finite float and double values use the quoted strings "NaN", "Infinity", and "-Infinity". Use explicit BigInteger or BigDecimal targets when arbitrary precision must be preserved.

Built-in representations

These built-in values use the following ordinary JSON shapes:

Java typeJSON representation
EnumConstant name as a string
Date, Calendar, java.sql.Date, Time, TimestampEpoch milliseconds as a number
TimeZoneTime-zone ID as a string
Java time and supported chronology date typesTheir standard textual form as a string
UUID, URI, File, Path, Locale, Charset, Currency, PatternType-specific text as a string; File and Path use path text, Locale uses a language tag, and Pattern does not retain flags
BitSetArray of signed long words from BitSet.toLongArray()
ByteBufferArray of signed byte values for the remaining range from position to limit
Optional and atomic wrappersTheir contained scalar, array, or value directly

Calendar reads epoch milliseconds into a new GregorianCalendar; its original calendar subtype, time zone, and other configuration are not retained. A null Optional reference and an empty Optional both write JSON null, and JSON null read as a declared Optional type becomes the corresponding empty Optional.

Dynamic JSON trees

Reading as Object uses natural JSON values:

JSON valueJava value
ObjectJsonObject
ArrayJsonArray
StringString
BooleanBoolean
Integer within long rangeLong
Larger integerBigInteger
Fraction or exponentDouble
Nullnull

JsonObject preserves member insertion order and JsonArray is mutable. They can also be created and written directly.

import org.apache.fory.json.JsonArray;
import org.apache.fory.json.JsonObject;

JsonObject object = new JsonObject();
JsonArray items = new JsonArray();
items.add(1);
items.add("two");
object.put("items", items);

String encoded = json.toJson(object);

Map keys

JSON object member names are strings. Declared map keys support String, byte, short, int, long, their boxed forms, and enums. A map declared with Object keys can write String, number, boolean, character, and enum keys, but reads them back as strings because JSON does not retain the original key type. Null map keys are rejected.

Builder configuration

Builder methodDefaultUser-visible effect
writeNullFields(boolean)falseDefault inclusion of null object properties
withCodegen(boolean)trueEnable generated object codecs
withAsyncCompilation(boolean)trueCompile generated codecs asynchronously
withFieldMode(boolean)falseWhen true, discover fields without getters/setters
withPropertyNamingStrategy(strategy)LOWER_CAMEL_CASEName properties without an explicit JsonProperty name
withMaxCachedFieldNames(int)DEFAULT_MAX_CACHED_FIELD_NAMES (8192)Field-name cache entries per reader; zero disables caching
withConcurrencyLevel(int)max(1, 2 * processors)Maximum concurrent root operations
withBufferSizeLimitBytes(int)2 MiBMaximum reusable capacity retained by each pooled writer
registerCodec(type, codec)NoneReplace the exact class's complete JSON codec
registerMixin(mixinType)NoneApply one annotation Mixin to its exact declared target

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 size, only reusable writer storage retained after an operation.

For class loading, type policy, nesting depth, graph-memory limits, and external input controls, see Fory JSON Security.

Builder mutation after build() does not modify an existing ForyJson runtime.

On Android, runtime code generation and asynchronous compilation are disabled. In a GraalVM native image, runtime compilation is unavailable; configurations returned by a reachable ForyJsonProvider use codecs generated while the image is built, and other configurations use interpreted codecs with build-time-prepared access metadata. Every other builder option keeps the behavior described above.