| --- |
| title: Custom Codecs |
| sidebar_position: 5 |
| id: custom-codecs |
| 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 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --- |
| |
| `JsonValueCodec<T>` is Fory JSON's streaming codec SPI for one complete JSON value. It writes |
| directly to Fory's String or UTF-8 writer and reads directly from Fory's Latin-1, UTF-16, or UTF-8 |
| reader. It is not a JSON abstract syntax tree (AST) or `JsonNode` codec. It owns the complete value, |
| including JSON null, but never handles a Map key; `MapKeyCodec` remains responsible for JSON object |
| member names. |
| |
| For an application codec with the same semantics in every representation, extend |
| `AbstractJsonValueCodec<T>` and implement the JSON shape once: |
| |
| ```java |
| import java.math.BigDecimal; |
| import org.apache.fory.json.codec.AbstractJsonValueCodec; |
| import org.apache.fory.json.reader.JsonReader; |
| import org.apache.fory.json.writer.JsonWriter; |
| |
| public final class MoneyCodec extends AbstractJsonValueCodec<Money> { |
| @Override |
| public void write(JsonWriter writer, Money value) { |
| if (value == null) { |
| writer.writeNull(); |
| } else { |
| writer.writeBigDecimal(value.amount); |
| } |
| } |
| |
| @Override |
| public Money read(JsonReader reader) { |
| return reader.tryReadNullToken() ? null : new Money(reader.readBigDecimal()); |
| } |
| } |
| |
| final class Money { |
| final BigDecimal amount; |
| |
| Money(BigDecimal amount) { |
| this.amount = amount; |
| } |
| } |
| ``` |
| |
| `AbstractJsonValueCodec` adds one virtual method call per operation. For a |
| performance-sensitive codec, or when behavior depends on a concrete reader or writer, implement |
| `JsonValueCodec<T>` directly and provide all five representation-specific methods. |
| |
| ```java |
| import org.apache.fory.json.ForyJson; |
| |
| ForyJson json = |
| ForyJson.builder() |
| .registerCodec(Money.class, new MoneyCodec()) |
| .build(); |
| ``` |
| |
| Exact `registerCodec` and exact-class factory registration are not allowed for types with |
| dedicated reader/writer operations: |
| |
| - `boolean`, `byte`, `short`, `int`, `long`, `float`, `double`, and `char`, including their boxed |
| classes |
| - `String`, `CharSequence`, `Number`, `BigInteger`, `BigDecimal`, and `UUID` |
| - `LocalDate`, `LocalTime`, `LocalDateTime`, `Instant`, `Duration`, `ZoneOffset`, `ZonedDateTime`, |
| `Year`, `YearMonth`, `MonthDay`, `Period`, `OffsetTime`, and `OffsetDateTime` |
| - `byte[]`, `String[]`, and `long[]` |
| |
| The restriction is exact; it does not include application subclasses. It also does not disable |
| occurrence-level `JsonCodec`, `JsonFormat`, or other semantic mappings. Use those mechanisms when a |
| field or parameter of one of these types needs a different representation. |
| |
| `ObjectCodec` instances belong to the resolver that created them and cannot be registered directly. |
| Use an exact `JsonCodecFactory` when a language module needs to supply an object model. |
| |
| Use `JsonCodecFactory` when one factory owns a family of declared or parameterized types: |
| |
| ```java |
| import org.apache.fory.json.JsonCodecFactory; |
| |
| JsonCodecFactory factory = |
| (type, resolver, runtimeType) -> |
| type.getRawType() == Money.class ? new MoneyCodec() : null; |
| |
| ForyJson json = |
| ForyJson.builder() |
| .withModule(context -> context.registerCodecFactory(factory)) |
| .build(); |
| ``` |
| |
| A configurable factory must override `factoryKey()` with a deterministic value covering every |
| option that can change the created codec class, object model, or generated operations. The default |
| factory class name is sufficient only for a configuration-free factory. |
| |
| `runtimeType` is `true` only when the factory is selecting a codec for the actual class of a value |
| during a dynamic write. Declared roots and composite child types receive `false`. A composite codec |
| that needs this distinction after construction must retain the flag for its later `resolveTypes` |
| call; it must not infer the value from resolver state. |
| |
| The containing property still controls its name, ignore direction, and null-inclusion policy. If a |
| null property is omitted, the value codec is not called. If the property is emitted, or the value |
| is an array element, collection element, map value, Optional value, or atomic-reference value, the |
| codec receives and owns null. The registered instance is shared across concurrent operations and |
| must be thread-safe. |
| |
| A custom codec that materializes composite graph owners must call `JsonReader.reserveGraphMemory` |
| with its application-defined byte estimate before creating each owner. This applies to composite |
| application objects, collections, maps, and reference arrays. Reserve collection and map reference |
| storage before the mutation that may grow it. A custom scalar or other dedicated leaf |
| representation, such as `MoneyCodec` above, does not reserve graph memory. Complete codecs also |
| perform their own application validation; Fory JSON does not wrap a complete custom representation |
| with the target type's `JsonValidator` methods. |
| |
| Registering a custom codec for a `JsonSubTypes` base replaces that base's subtype annotation. |
| Registering one for a listed subtype is supported by the two wrapper inclusions but not by inline |
| property inclusion. |
| |
| ## Selecting Codecs with `JsonCodec` |
| |
| Use `@JsonCodec` on a class, record, enum, or interface to declare its default complete-value |
| codec. The positional form is shorthand for `value`: |
| |
| ```java |
| import org.apache.fory.json.annotation.JsonCodec; |
| |
| @JsonCodec(MoneyCodec.class) |
| public final class Money {} |
| |
| @JsonCodec(AccountCodec.class) |
| public interface Account {} |
| |
| public final class RetailAccount implements Account {} |
| ``` |
| |
| Type declarations are inherited through both superclasses and interfaces. The most-specific |
| declaration wins. Unrelated declarations using the same codec are consistent; unrelated |
| declarations using different codecs fail instead of depending on reflection order. |
| |
| On a field or effective ordinary getter, `value` replaces the complete property value. The same |
| annotation is supported on an effective setter value parameter, a `JsonCreator` constructor or |
| factory parameter, and a record component through Java's field, accessor, and constructor-parameter |
| propagation: |
| |
| ```java |
| public final class Invoice { |
| @JsonCodec(MoneyCodec.class) |
| public Money total; |
| private Money tax; |
| private Money discount; |
| |
| @JsonCodec(MoneyCodec.class) |
| public Money getTax() { |
| return tax; |
| } |
| |
| public void setDiscount(@JsonCodec(MoneyCodec.class) Money discount) { |
| this.discount = discount; |
| } |
| |
| @JsonCreator |
| public Invoice(@JsonProperty("total") @JsonCodec(MoneyCodec.class) Money total) { |
| this.total = total; |
| } |
| } |
| ``` |
| |
| Use a child member when the standard container should remain in control and only its direct child |
| needs a custom codec: |
| |
| ```java |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.concurrent.atomic.AtomicReferenceArray; |
| |
| public final class InvoiceGroup { |
| @JsonCodec(elementCodec = MoneyCodec.class) |
| public List<Money> items; |
| |
| @JsonCodec(elementCodec = MoneyCodec.class) |
| public Money[] itemArray; |
| |
| @JsonCodec(elementCodec = MoneyCodec.class) |
| public AtomicReferenceArray<Money> atomicItems; |
| |
| @JsonCodec(contentCodec = MoneyCodec.class) |
| public Optional<Money> optional; |
| |
| @JsonCodec(contentCodec = MoneyCodec.class) |
| public AtomicReference<Money> current; |
| |
| @JsonCodec(keyCodec = CurrencyKeyCodec.class, valueCodec = MoneyCodec.class) |
| public Map<Currency, Money> byCurrency; |
| } |
| ``` |
| |
| The child members have these meanings: |
| |
| | Member | Supported current value | Direct child handled by the codec | |
| | -------------- | ------------------------------------------------- | --------------------------------- | |
| | `elementCodec` | `Collection<E>`, `E[]`, `AtomicReferenceArray<E>` | `E` | |
| | `contentCodec` | `Optional<T>`, `AtomicReference<T>` | `T` | |
| | `keyCodec` | `Map<K, V>` | JSON member name for `K` | |
| | `valueCodec` | `Map<K, V>` | direct `V` value | |
| |
| ### Kotlin occurrences |
| |
| Kotlin uses the same codec registrations and `JsonCodec` annotation. Apply property annotations to |
| an explicit supported use site, for example: |
| |
| ```kotlin |
| import org.apache.fory.json.annotation.JsonCodec |
| |
| data class Ledger( |
| @field:JsonCodec(value = MoneyCodec::class) |
| val total: Money, |
| @field:JsonCodec(elementCodec = MoneyCodec::class) |
| val entries: List<Money>, |
| ) |
| ``` |
| |
| The complete-value codec owns the whole JSON value. Child codecs leave the standard array, |
| collection, Optional/atomic, or map representation in control. Kotlin nullability is still the |
| declared occurrence contract around a selected application codec: after a non-null JSON token, the |
| codec must return the exact declared type and must not return null for a non-null occurrence. |
| |
| An unsigned or eligible value-class map key can use the built-in member-name mapping without an |
| annotation. Use an explicit `keyCodec` or whole-map codec when the key has a different tagged text |
| shape. An exact application registration still takes precedence over the Kotlin module defaults. |
| |
| A custom Map-key codec converts between the declared key and a JSON member name: |
| |
| ```java |
| import java.util.Locale; |
| import org.apache.fory.json.codec.MapKeyCodec; |
| |
| public final class CurrencyKeyCodec implements MapKeyCodec { |
| @Override |
| public String toName(Object key) { |
| return ((Currency) key).name().toLowerCase(Locale.ROOT); |
| } |
| |
| @Override |
| public Object fromName(String name) { |
| return Currency.valueOf(name.toUpperCase(Locale.ROOT)); |
| } |
| } |
| ``` |
| |
| Code that used the removed type-use form should move the codec to the owning declaration: |
| |
| ```java |
| // Before |
| List<@JsonCodec(MoneyCodec.class) Money> items; |
| |
| // Now |
| @JsonCodec(elementCodec = MoneyCodec.class) |
| List<Money> items; |
| ``` |
| |
| Use `contentCodec` for an `Optional` or `AtomicReference`, `valueCodec` for a Map value, and |
| `elementCodec` for an array or `AtomicReferenceArray` element. |
| |
| `Iterable<E>` values that are not `Collection<E>` do not support `elementCodec`. Use `value` when a |
| complete codec should own such a value. |
| |
| Child configuration is intentionally one level deep. For `List<List<Money>>`, `elementCodec` |
| handles each complete `List<Money>`. For `Money[][]`, it handles each `Money[]`. To customize a |
| deeper descendant, implement a codec for the complete current value and select it with `value`. |
| |
| `value` is mutually exclusive with every child member because it already owns the complete current |
| value. An empty annotation, an unsupported child member, or an outer complete codec combined with |
| a child member fails during model construction. A configured direct child must resolve to a |
| concrete type; raw containers, direct wildcards, and unresolved direct type variables are rejected. |
| |
| `JsonAnyProperty` and `JsonAnyGetter` flatten their Map into the enclosing object. Configure their |
| dynamic values with `valueCodec`: |
| |
| ```java |
| @JsonAnyProperty |
| @JsonCodec(valueCodec = MoneyCodec.class) |
| public Map<String, Money> extra; |
| ``` |
| |
| The first `JsonAnySetter` parameter is the String property name. Its second parameter may use |
| `@JsonCodec(value = ...)` or another configuration valid for that parameter's own shape. |
| |
| ## Codec Precedence and Repeated Declarations |
| |
| Fory resolves each current value in this order: |
| |
| | Priority | Source | |
| | -------: | ------------------------------------------------- | |
| | 1 | Current property or parameter `JsonCodec` | |
| | 2 | Exact `registerCodec` registration | |
| | 3 | Exact-target type `JsonCodec` after Mixin overlay | |
| | 4 | Inherited-frontier type `JsonCodec` declaration | |
| | 5 | Built-in or default JSON mapping | |
| |
| One logical property may expose the annotation from its field, getter, setter parameter, creator |
| parameter, or record propagation. Repeated configurations must be identical; Fory does not merge |
| partial configurations from different declarations. An unannotated effective override suppresses |
| an inherited method annotation. |
| |
| A child member replaces only that direct child. Unconfigured Map siblings continue through the |
| normal precedence. If an exact registration or type declaration supplies a complete codec for the |
| outer container, a property child member is unreachable and therefore rejected. |
| |
| Map keys are JSON object member names and use `MapKeyCodec`, not `JsonValueCodec`. A custom key |
| codec class follows the same construction rules as a value codec. Null Map keys are rejected, and |
| decoded keys must match the declared key type. |
| |
| ## Codec Construction and Platform Support |
| |
| An annotation codec class must be public, concrete, top-level or static nested, and have a public |
| no-argument constructor. One instance is shared by all annotated sites and concurrent operations of |
| the built `ForyJson`, so it must be thread-safe. For an eligible type, use |
| `registerCodec(Target.class, instance)` when a complete-value codec needs configuration. |
| |
| Outside GraalVM Native Image, a named Java module must export or open the codec package to |
| `org.apache.fory.json`. Native Image prepares annotation-codec constructors during image |
| construction and does not require that package access. When an inherited type-declaration codec is |
| used for a more specific target, every decoded value must be null or assignable to that target. |
| |
| The annotation has the same FIELD, METHOD, and PARAMETER behavior on the JVM, Android, and GraalVM |
| Native Image. Ordinary Android classes may omit `JsonType` and provide equivalent exact rules. |
| Android-desugared Records, including `JsonValue` Records, require processor-generated operations |
| from either a direct `JsonType` declaration or a compiled exact `JsonMixin` pair. GraalVM object |
| models follow the build-time workflow in the |
| [GraalVM guide](graalvm.md). |