title: Scala sidebar_position: 7 id: scala license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Fory JSON supports Scala 2.13 and Scala 3 through the optional fory-json-scala artifact. The module works on the ordinary JVM and GraalVM Native Image. Android is not supported.
libraryDependencies += "org.apache.fory" %% "fory-json-scala" % "1.6.1"
ForyJsonScala.builder() installs the Scala module and returns the standard Fory JSON builder:
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 text = json.toJson(Person("Ada")) val person = json.fromJson(text, classOf[Person])
Reuse the resulting ForyJson instance. It is immutable and thread-safe after construction.
Case classes are decoded by calling their full primary constructor. Fory invokes Scala's generated constructor-default methods for missing defaulted parameters; it does not parse default expressions or mutate constructor val fields. Defaults in later parameter lists receive the preceding constructor arguments exactly as Scala defines them. A missing parameter without a default is an error. Mutable body properties are applied after construction.
Fory JSON annotations can be placed directly on Scala constructor properties:
import org.apache.fory.json.annotation.{JsonCodec, JsonIgnore, JsonProperty} case class Media( @JsonProperty("media_uri") uri: String, @JsonIgnore internalId: String = "hidden", @JsonCodec(elementCodec = classOf[TagCodec]) tags: List[Tag] = Nil, @JsonProperty(include = JsonProperty.Include.NON_NULL) title: String = null )
JsonIgnore applies to fields, property methods, setter parameters, and selected constructor parameters. JsonCodec child slots bind direct collection elements, Option content, and map keys or values. All other Fory JSON annotations retain the behavior described in Annotations.
If a required non-defaulted reference parameter uses an inclusion rule that would omit null, serialization rejects a null value. This guarantees that JSON written by Fory remains readable by the same case-class schema.
| Scala type | JSON representation |
|---|---|
Unit | null |
| case class | object |
| singleton object | empty object |
| value class | underlying value |
Option[A], Some[A], None | contained value or null |
Either[L, R] | object containing exactly one l or r member |
List, Seq, Vector, Queue, ArraySeq, buffers, sets | array |
Scala maps, IntMap, LongMap | object |
immutable and mutable BitSet | ascending integer array |
Tuple1 through Tuple22 | fixed-length array |
Scala 3 EmptyTuple | empty array |
BigInt, BigDecimal | JSON number |
Scala StringBuilder | string |
Range, supported NumericRange | realized value array |
FiniteDuration, Duration | fixed length/unit or special object |
| parameterless Scala 3 enum | string case name |
Scala 2 Enumeration | string through an owner-bound codec |
Strict standard-library collections are reconstructed through their standard Scala builders. Either writes compact l and r member names. Readers also accept the legacy left and right member names. Fory does not add a Scala-specific collection-size limit; the codecs use the same input-length, depth, graph-memory, and read-progress limits as Fory JSON core. A sparse BitSet whose highest index would require backing storage disproportionate to the available JSON input is rejected.
Lazy or process-local values are intentionally unsupported by the default module, including LazyList, Stream, views, iterators, collection builders, Try, Throwable, Future, Promise, ExecutionContext, Deadline, functions, reflection/compiler metadata, and regex values. Sorted or custom collections need an exact application codec because their ordering or construction is application configuration.
Use a complete TypeRef when reading a parameterized Scala type:
import org.apache.fory.reflect.TypeRef val typeRef = new TypeRef[Map[String, Option[Int]]]() {} val value = json.fromJson("""{"count":1}""", typeRef)
Scala raw strings can be passed directly to fromJson; JSON double quotes do not need backslash escaping.
Scala value-type arguments can erase to Object in a normal JVM signature. ScalaTypeRef is a compile-time type-token constructor that preserves those arguments on Scala 2.13 and Scala 3:
import org.apache.fory.json.scala.ScalaTypeRef val rangeType = ScalaTypeRef[scala.collection.immutable.NumericRange[Int]] val range = json.fromJson("[1,3,5,7]", rangeType)
Some[Int] is a valid declared type when supplied with its complete type argument. A non-null JSON value decodes to Some(value); JSON null is rejected for Some[Int] but decodes to None for Option[Int].
Scala 2 erases the owning Enumeration from Enumeration#Value. Use JsonEnumeration to retain the owner on a direct value, collection or array element, Option content, or map key/value:
import org.apache.fory.json.scala.JsonEnumeration object Weekday extends Enumeration { val Monday, Tuesday = Value } object Month extends Enumeration { val January, February = Value } case class Schedule( @JsonEnumeration(classOf[Weekday.type]) day: Weekday.Value, @JsonEnumeration(element = classOf[Weekday.type]) days: List[Weekday.Value], @JsonEnumeration(content = classOf[Month.type]) month: Option[Month.Value], @JsonEnumeration( mapKey = classOf[Weekday.type], mapValue = classOf[Month.type] ) labels: Map[Weekday.Value, Month.Value] )
Each slot describes one direct Enumeration.Value occurrence. value cannot be combined with a child slot, and element, content, and map slots must match the annotated property's immediate type shape. Invalid or conflicting declarations fail when the case-class metadata is created.
For a custom wire representation, extend ScalaEnumerationCodec and select the codec through @JsonCodec. The codec also implements the map-key contract, so its class can be used in keyCodec.
A parameterless Scala 3 enum uses its case name as a JSON string. Add derives ScalaJsonCodec to an enum with parameterized cases to define one closed wrapper-object representation for every case:
import org.apache.fory.json.scala.* enum Result derives ScalaJsonCodec { case Ok(value: String) case Error(code: Int) case Pending } val json = ForyJsonScala.builder().build()
The values above use {"Ok":{"value":"ready"}}, {"Error":{"code":7}}, and {"Pending":{}}. The reader never accepts a class name or chooses a subtype from runtime reflection. For a third-party enum that cannot add derives, derive and register its schema at the builder call site:
val json = ForyJsonScala.builder().register[thirdparty.Result].build()
A library that supports several third-party Scala 3 enums can package their derived codecs in a reusable module:
import org.apache.fory.json.{ForyJsonModule, ModuleContext} import org.apache.fory.json.scala.* object ThirdPartyJsonModule extends ForyJsonModule: override def install(context: ModuleContext): Unit = context.registerCodec( classOf[thirdparty.Result], ScalaJsonCodec.derived[thirdparty.Result] ) val json = ForyJsonScala.builder() .withModule(ThirdPartyJsonModule) .build()
The derivation is compiled as part of the module, so consumers only install the compiled module. This is the reusable equivalent of calling register[thirdparty.Result] on one builder.
Modules are installed explicitly with withModule. Fory JSON does not scan the classpath or invoke modules through ServiceLoader; explicit installation keeps the enabled codecs deterministic and prevents an unrelated dependency from changing deserialization behavior. See Modules for the general module API and registration rules.
The Scala module uses the same build-time module registration on the JVM and in a native image. Application models, custom codecs, and derived enum schemas must be reachable when the native image is built. Generate Fory codecs as part of the native-image build rather than adding general reflection configuration. No Scala compiler, TASTy reader, or runtime macro execution is required.