Skip to content

Latest commit

 

History

History
154 lines (108 loc) · 4.25 KB

json.md

File metadata and controls

154 lines (108 loc) · 4.25 KB

JSON serialisation

alloy defines a number of traits that can be taken into consideration by protocols to express additional constraints and encodings typically found in the industry.

Unions

Unions in this protocol can be encoded in three different ways: tagged, discriminated, and untagged.

By default, the specification of the Smithy language hints that the tagged-union encoding should be used. This is arguably the best encoding for unions, as it works with members of any type (not just structures), and does not require backtracking during parsing, which makes it more efficient.

However, alloy#simpleRestJson supports two additional encodings: discriminated and untagged, which users can opt-in via the alloy#discriminated and alloy#untagged trait, respectively. These are mostly offered as a way to retrofit existing APIs in Smithy.

Tagged union

This is the default behavior, and happens to visually match how Smithy unions are declared. In this encoding, the union is encoded as a JSON object with a single key-value pair, the key signalling which alternative has been encoded.

union Tagged {
  first: String
  second: IntWrapper
}

structure IntWrapper {
  int: Integer
}

The following instances of Tagged

Tagged.FirstCase("alloy")
Tagged.SecondCase(IntWrapper(42)))

are encoded as such :

{ "first": "alloy" }
{ "second": { "int": 42 } }

Untagged union

Untagged unions are supported via an annotation: @untagged. Despite the smaller payload size this encoding produces, it is arguably the worst way of encoding unions, as it may require backtracking multiple times on the parsing side. Use this carefully, preferably only when you need to retrofit an existing API into Smithy.

use alloy#untagged

@untagged
union Untagged {
  first: String
  second: IntWrapper
}

structure IntWrapper {
  int: Integer
}

The following instances of Untagged

Untagged.FirstCase("alloy")
Untagged.SecondCase(Two(42)))

are encoded as such :

"alloy"
{ "int": 42 }

Discriminated union

Discriminated union are supported via an annotation: @discriminated("tpe"), and work only when all members of the union are structures. In this encoding, the discriminator is inlined as a JSON field within JSON object resulting from the encoding of the member.

Despite the JSON payload exhibiting less nesting than in the tagged union encoding, this encoding often leads to bigger payloads, and requires backtracking once during parsing.

use alloy#discriminated

@discriminated("tpe")
union Discriminated {
  first: StringWrapper
  second: IntWrapper
}

structure StringWrapper {
  myString: String
}

structure IntWrapper {
  myInt: Integer
}

The following instances of Discriminated

Discriminated.FirstCase(StringWrapper("alloy"))
Discriminated.SecondCase(IntWrapper(42)))

are encoded as such

{ "tpe": "first", "myString": "alloy" }
{ "tpe": "second", "myInt": 42 }

Null values

The standard Smithy toolset does not provide any semantics for distinguishing between a JSON field being set to null and the same field being absent from its carrying JSON object. However, depending on the use-case, the difference can be meaningful. In order to support such use-cases, the additional trait alloy.nullable is provided. Annotating the member of a structure field with this indicates that a value serialised to null was a conscious decision (as opposed to omitting the value altogether), and that deserialisation should retain this information.

For example, assuming the following smithy structure

use alloy#nullable

structure Foo {
  @nullable
  nullable: Integer
  regular: Integer
}

The JSON objects

{ "nullable": null, "regular": null }
{ "nullable": 4, "regular": 4 }
{}

are respectively decoded as follows in Scala (when using smithy4s):

Foo(Some(Nullable.Null), None)
Foo(Some(Nullable.Value(4)), Some(4))
Foo(None, None)

or some similar type which preserves the information that an explicit null was passed. These objects are in turn encoded as

{ "nullable": null }
{ "nullable": 4, "regular": 4 }
{}

This means that @nullable allows round-tripping null values.