# `@json` auto-generated encoders, decoders
The `@json` decorator is a macro that auto-generates encoders and decoders for a Temper type.
Given definitions in std/json of an *encoder* and a *decoder*.
```ts
interface JsonAdapter<T> {
/** Encodes a *T* to JSON */
public encodeToJson(x: T, o: JsonProducer): Void;
/** Decodes a *T* from a JSON AST */
public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): T | Bubble;
}
```
Below are example of how the generators work for each of these strategies:
For a `sealed interface` declaration, we know the sub-types, so decoding and encoding delegate to sub-type encoders and decoders.
The auto-generated encoder switches on the type and delegate to the appropriate sub-type's encoder. Their decoders, given a *JsonObject*, look at its property names (and/or [discriminants](#Discriminants-and-extra-properties)) and try to use those to narrow which sub-type decoders to try, but otherwise fall back to trying the decoders in order and passing the first one that does not bubble.
For a `class` type whose backed properties correspond to constructor parameters, we know the backed property names and whether they correspond to constructor properties. If they do, we can generate an encoder that uses those property names as JSON property keys.
For an interface type with a `toJson` method, our encoder can delegate to it. If it has a static `fromJson` method, our decoder can delegate to it.
In all cases, where the type declaration has a type parameter that does not have
## sealed interface example
Given a sealed type, if the following are true, then we can produce an adapter that delegates to sub-types:
- every direct sub-types has its own adapter or is a sealed interface that does not have this property but which recursively has this property.
More precisely, walking the sub-type tree, along every branch, runs into a type with an adapter without crossing over a non-sealed interface type
- For each sub-type enumerable above, *sub*, of sealed type *st*, we can derive an adapter expression for each of σ's generic type parameters from a suite of *st*'s type parameters' adapters.
- There does not exist a JSON text that passes more than one of the adapters of the enumerable sub-types above.
The first two properties are statically checkable, but the last property is not.
```ts
@json
sealed interface BasePoint {
...
}
@json
class CartesianPoint(
public let x: Float64,
public let y: Float64
) {
...
}
@json
class PolarPoint(
public let theta: Float64,
public let radius: Float64,
) {
...
}
```
Since the two sub-types are also `@json`, and their backed property names are disjoint, we can derive a JSON adapter for *BasePoint* thus.
```ts
// Added to BasePoint
{
public static jsonAdapter(): JsonAdapter<BasePoint> {
return new BasePointAdapter();
}
}
class BasePointAdapter : JsonAdapter<BasePoint> {
public decode(t: JsonSyntaxTree, ic: InterchangeContext): BasePoint | Bubble {
let o = t.as<JsonObject>();
// Inferred by sub-type inspection
// that `.has("x")` is sufficient
// to distinguish catesian from other
// declared types.
if (o.properties.has("x")) {
CartesianPoint.jsonAdapter()
.decode(t, ic)
} else {
PolarPoint.jsonAdapter()
.decode(t, ic)
}
}
public encode(p: BasePoint, o: JsonProducer): Void {
when (p) {
is CartesianPoint -> CartesianPoint.jsonAdapter()
.encode(p, o);
is PolarPoint -> PolarPoint.jsonAdapter()
.encode(p, o);
}
}
}
```
## class Type
When a class type has the following properties, we can generate an adapter as below.
- Each backed property that needs to be serialized corresponds to a constructor parameter.
```ts
@json
class CartesianPoint(
public let x: Float64,
public let y: Float64
) {
...
}
```
We can generate an adapter for that type as below:
```ts
// Added to CartesianPoint
{
public static jsonAdapter(): JsonAdapter<BasePoint> {
new CartesianPointAdapter()
}
// The adapter class calls back into this
// class to do encoding so that it can
// access private properties.
public toJson(o: JsonProducer): Void {
let float64Adapter = Float64.jsonAdapter();
o.startObject();
o.objectKey("x");
float64Adapter.encode(this.x);
o.objectKey("y");
float64Adapter.encode(this.y);
o.endObject();
}
}
class CartesianPointAdapter : JsonAdapter<BasePoint> {
public decode(t: JsonSyntaxTree, ic: InterchangeContext): CartesianPoint | Bubble {
let o = t.as<JsonObject>();
let float64Adapter = Float64.jsonAdapter();
let x =
float64Adapter.decode(t.properties.get("x"), ic);
let y = float64Adapter.decode(t.properties.get("y"), ic);
return new CartesianPoint(x, y);
}
public encode(p: CartesianPoint, o: JsonProducer): Void {
p.toJson(o, /* No adapters to pass */);
}
}
```
## Unsealed interface type
We can generate an adapter for a type for which the preceding strategies do not work if it satisfies the following method requirements:
- It provides or inherits a `public toJson(...): Void` with the right arguments.
- It provides a `public static fromJson(...): T` with the right arguments and return type.
The *VeryAbstract* type below has an abstract property and satisfies the requirements above.
```ts
interface VeryAbstract {
public i: Int;
public toJson(p: JsonProducer): Void {
p.startObject();
p.objectKey("i");
p.intValue(this.i);
p.endObject();
}
public static fromJson(t: JsonSyntaxTree, ic: InterchangeContext): VeryAbstract | Bubble {
let i = t.as<Object>().properties.get("i").as<JsonInt>().content;
new VeryConcreteImpl(i)
}
}
```
From that, we can generate a *jsonAdapter()* as below.
```ts
// Added to VeryAbstract
{
public static jsonAdapter(): JsonAdapter<VeryAbstract> {
new VeryAbstractAdapter()
}
}
class VeryAbstractAdapter extends
JsonAdapter<VeryAbstract> {
public decode(t: JsonSyntaxTree, ic: InterchangeContext): VeryAbstract | Bubble {
VeryAbstract.decode(t, ic)
}
public encode(a: VeryAbstract, o: JsonProducer): Void {
a.encode(o)
}
}
```
## Generic type adapters.
Given a generic type, for some subset of type parameters (for the initial implementation, all of them), we want a generated adapter to close over adapters for those type parameters.
This works when the type parameters are invariant. TODO: can we do better or would we need to separate *Adapter* into *Encoder* and *Decoder* classes?
By convention, when passing adapters for type parameters, the parameter adapters follow the regular arguments.
Here's an example of how this works.
```ts
class UnnecessaryListWrapper<T>(
let list: List<T>
) {
...
}
```
Here's how the extra adapter gets threaded about to where it's needed.
```ts
{
public static jsonAdapter<T>(
// Adapter for T
adapterT: JsonAdapter<T>
): JsonAdapter<UnnecessaryListWrapper<T>> {
new UnnecessaryListWrapperAdapter<T>(adapterT)
}
public toJson(
p: JsonProducer,
adapterT: JsonAdapter<T>
): Void {
let listAdapter: JsonAdapter<List<T>> = List.jsonAdapter(adapterT);
... // Use listAdapter to adapt this.list
}
}
class UnnecessaryListWrapperAdapter<T> extends Adapter<UnnecessaryListWrapper<T>>(
let adapterT: <JsonAdapter<T>>
) extends JsonAdapter<UnecessarlyListWrapper<T>> {
public decode(t: JsonSyntaxTree, ic: InterchangeContext): UnnecessaryListWrapper<T> | Bubble {
let listAdapter: JsonAdapter<List<T>> = List.jsonAdapter(adapterT);
... // Use listAdapter to adapt a List<T>
}
public encode(w: UnnecessaryListWrapper<T>, o: JsonProducer): Void {
w.toJson(o, adapterT)
}
}
```
# Discriminants and extra properties
Decoding a sealed type works really well when the set of properties available in a JSON object uniquely identifies each concrete sub-type.
Like a tagged union's type tag, a discriminant is a property that has a different, fixed value for each variant of the union type.
But that's not always the case.
Also, sometimes there is an existing discriminant. [Jackson's Java *\@JsonTypeInfo* annotation](https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html) allows embedding a property like `"class": "com.example.Foo"` as part of the encoded form. That allows looking up `com.example.Foo` by name using Java reflection when decoding.
The *\@jsonExtra* decoration informs any auto-generated adapter that the extra properties must be present on the encoded form.
```ts
@json
@jsonExtra("key", "value")
class Foo {}
```
With that decoration, the JSON form of *Foo* looks like the below.
```json
{
"key": "value"
}
```