Skip to main content

Fory v1.7.0 Released

· 7 min read
Shawn Yang
Apache Fory PMC Chair

The Apache Fory team is pleased to announce the 1.7.0 release. This release includes 26 PRs. See the Getting Started page to get the libraries for your platform.

Highlights​

  • Added Fory JSON support for Scala 2.13 and Scala 3.
  • Added Fory JSON support for Kotlin on Android, the JVM, and GraalVM Native Image.
  • Enhanced Fory JSON on GraalVM Native Image with build-time code generation enabled by default and fine-grained generated codec caching.
  • Added incremental JSON stream decoding for top-level arrays and newline-delimited JSON (NDJSON), allowing values to be processed progressively from chunked UTF-8 input.
  • Expanded Swift platform support to visionOS, watchOS, tvOS, and Linux, and added Swift gRPC code generation.

JSON Support for Scala​

Fory 1.7.0 introduces fory-json-scala for Scala 2.13 and Scala 3. Scala applications can read and write standard JSON using case classes, constructor defaults, Option, Either, tuples, collections, maps, and value classes. The module works on the JVM and GraalVM Native Image.

Add the Scala JSON module to your sbt build:

libraryDependencies += "org.apache.fory" %% "fory-json-scala" % "1.7.0"

Create a reusable ForyJson instance with ForyJsonScala.builder(). Missing defaulted parameters use Scala's compiler-generated constructor defaults, so immutable case classes do not need a zero-argument constructor or mutable fields:

import org.apache.fory.json.scala.ForyJsonScala

case class Person(name: String, age: Int = 18, aliases: List[String] = Nil)

val json = ForyJsonScala.builder().build()
val person = json.fromJson("""{"name":"Ada"}""", classOf[Person])
assert(person == Person("Ada", 18, Nil))

val text = json.toJson(person)

Fory JSON annotations work on Scala constructor properties. Scala 2 Enumeration values can use JsonEnumeration to retain their owning enumeration, including values inside collections and maps. On Scala 3, derives ScalaJsonCodec supports enums with parameterized cases and, together with JsonSubTypes, sealed hierarchies whose allowed subtypes are declared by the application.

Use a complete TypeRef for parameterized types, or ScalaTypeRef when Scala value-type arguments would otherwise be erased. See the Scala JSON guide for supported types, annotations, and Native Image setup.

JSON Support for Kotlin​

The new fory-json-kotlin module maps Kotlin models to standard JSON while preserving constructor defaults, nullability, unsigned types, value classes, and generic arguments. It supports the JVM, Android API 26 and later, and GraalVM Native Image without requiring kotlin-reflect.

Add the runtime dependency:

dependencies {
implementation("org.apache.fory:fory-json-kotlin:1.7.0")
}

Use jsonTypeRef<T>() to retain Kotlin type information at the root and in nested values. Ordinary Java type tokens cannot represent every Kotlin distinction, such as nullable collection elements or a value class lowered to a primitive:

import org.apache.fory.json.kotlin.ForyJsonKotlin
import org.apache.fory.json.kotlin.jsonTypeRef

data class User(
val id: ULong,
val name: String,
val nickname: String? = null,
)

val json = ForyJsonKotlin.builder().build()
val userType = jsonTypeRef<User>()

val user = json.fromJson("""{"id":7,"name":"Alice"}""", userType)
val text = json.toJson(user, userType)

A missing member invokes its constructor default when one exists. An explicit JSON null is checked against the Kotlin declaration and never requests a default. Fory calls the model's constructor, so initialization and validation still run. Sealed classes and interfaces can use JsonSubTypes to declare a closed set of logical subtype names.

On Android, runtime JSON code generation is disabled. Use the fory-json-kotlin-ksp processor when R8 or ProGuard shrinks Kotlin models, together with JsonType on application models or an exact JsonMixin for third-party targets. For Native Image, install ForyJsonKotlin in a reachable ForyJsonProvider configuration and make the required models and exact generic bindings reachable at build time. See the Kotlin JSON guide, Android guide, and GraalVM Native Image guide for setup and supported configurations.

Incremental JSON Stream Decoding​

Fory JSON can now decode a top-level array or NDJSON stream as UTF-8 chunks arrive. Applications can process each completed element or record without buffering the complete document or waiting for the end of the stream. Chunks are supplied as ByteBuffer instances and can end partway through a JSON value.

Use newArrayStreamDecoder for one top-level JSON array. Each successful decodeNext call exposes one decoded element through value():

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.JsonStreamDecoder;

public final class User {
public long id;
public String name;
}

ForyJson json = ForyJson.builder().build();
JsonStreamDecoder<User> decoder =
json.newArrayStreamDecoder(User.class, 1024 * 1024);

ByteBuffer[] chunks = {
ByteBuffer.wrap("[{\"id\":1,\"name\":\"Ada\"},".getBytes(StandardCharsets.UTF_8)),
ByteBuffer.wrap("{\"id\":2,\"name\":\"Al".getBytes(StandardCharsets.UTF_8)),
ByteBuffer.wrap("ice\"}]".getBytes(StandardCharsets.UTF_8))
};

for (ByteBuffer chunk : chunks) {
while (decoder.decodeNext(chunk)) {
User user = decoder.value();
System.out.println(user.id + ": " + user.name);
}
}
decoder.finish();

Use newNdjsonStreamDecoder for records separated by LF or CRLF. Call finish() at the end of input and consume its value when it returns true: this handles a final record without a trailing newline.

JsonStreamDecoder<User> decoder =
json.newNdjsonStreamDecoder(User.class, 1024 * 1024);

ByteBuffer chunk = ByteBuffer.wrap(
("{\"id\":1,\"name\":\"Ada\"}\n"
+ "{\"id\":2,\"name\":\"Alice\"}").getBytes(StandardCharsets.UTF_8));
while (decoder.decodeNext(chunk)) {
User user = decoder.value();
System.out.println(user.id + ": " + user.name);
}
if (decoder.finish()) {
User user = decoder.value();
System.out.println(user.id + ": " + user.name);
}

Drain each chunk before supplying the next one. The required maxValueBytes argument limits each array element or NDJSON record, rather than the complete stream. A decoder belongs to one stream, is not thread-safe, and cannot be reused after completion or failure. See Incremental JSON streams for buffer ownership, null values, and byte-limit details.

Features​

Bug Fix​

Other Improvements​

New Contributors​

Full Changelog: https://github.com/apache/fory/compare/v1.6.1...v1.7.0