Skip to content

Optional decoder update #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 10 additions & 69 deletions docs/classes/_decoder_.decoder.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ Alternatively, the main decoder `run()` method returns an object of type `Result

## Index

### Constructors

* [constructor](_decoder_.decoder.md#constructor)

### Properties

* [decode](_decoder_.decoder.md#decode)
Expand All @@ -43,7 +39,6 @@ Alternatively, the main decoder `run()` method returns an object of type `Result
* [number](_decoder_.decoder.md#number)
* [object](_decoder_.decoder.md#object)
* [oneOf](_decoder_.decoder.md#oneof)
* [optional](_decoder_.decoder.md#optional)
* [string](_decoder_.decoder.md#string)
* [succeed](_decoder_.decoder.md#succeed)
* [union](_decoder_.decoder.md#union)
Expand All @@ -52,28 +47,6 @@ Alternatively, the main decoder `run()` method returns an object of type `Result

---

## Constructors

<a id="constructor"></a>

### `<Private>` constructor

⊕ **new Decoder**(decode: *`function`*): [Decoder](_decoder_.decoder.md)

The Decoder class constructor is kept private to separate the internal `decode` function from the external `run` function. The distinction between the two functions is that `decode` returns a `Partial<DecoderError>` on failure, which contains an unfinished error report. When `run` is called on a decoder, the relevant series of `decode` calls is made, and then on failure the resulting `Partial<DecoderError>` is turned into a `DecoderError` by filling in the missing informaiton.

While hiding the constructor may seem restrictive, leveraging the provided decoder combinators and helper functions such as `andThen` and `map` should be enough to build specialized decoders as needed.

**Parameters:**

| Param | Type |
| ------ | ------ |
| decode | `function` |

**Returns:** [Decoder](_decoder_.decoder.md)

___

## Properties

<a id="decode"></a>
Expand Down Expand Up @@ -331,6 +304,7 @@ Providing the type parameter is only necessary for type-literal strings and numb
| constant(true) | Decoder<true> |
| constant(false) | Decoder<false> |
| constant(null) | Decoder<null> |
| constant(undefined) | Decoder<undefined> |
| constant('alaska') | Decoder<string> |
| constant<'alaska'>('alaska') | Decoder<'alaska'> |
| constant(50) | Decoder<number> |
Expand All @@ -349,12 +323,13 @@ interface Bear {
isBig: boolean;
}

const bearDecoder1: Decoder<Bear> = object({
const bearDecoder1 = object<Bear>({
kind: constant('bear'),
isBig: boolean()
});
// Type 'Decoder<{ kind: string; isBig: boolean; }>' is not assignable to
// type 'Decoder<Bear>'. Type 'string' is not assignable to type '"bear"'.
// Types of property 'kind' are incompatible.
// Type 'Decoder<string>' is not assignable to type 'Decoder<"bear">'.
// Type 'string' is not assignable to type '"bear"'.

const bearDecoder2: Decoder<Bear> = object({
kind: constant<'bear'>('bear'),
Expand Down Expand Up @@ -561,40 +536,6 @@ oneOf(constant('start'), constant('stop'), succeed('unknown'))

**Returns:** [Decoder](_decoder_.decoder.md)<`A`>

___
<a id="optional"></a>

### `<Static>` optional

▸ **optional**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)< `undefined` &#124; `A`>

Decoder for values that may be `undefined`. This is primarily helpful for decoding interfaces with optional fields.

Example:

```
interface User {
id: number;
isOwner?: boolean;
}

const decoder: Decoder<User> = object({
id: number(),
isOwner: optional(boolean())
});
```

**Type parameters:**

#### A
**Parameters:**

| Param | Type |
| ------ | ------ |
| decoder | [Decoder](_decoder_.decoder.md)<`A`> |

**Returns:** [Decoder](_decoder_.decoder.md)< `undefined` &#124; `A`>

___
<a id="string"></a>

Expand Down Expand Up @@ -812,18 +753,18 @@ decoder.run({a: {x: 'cats'}})
// => {ok: false, error: {... at: 'input.a.b[0]' message: 'path does not exist'}}
```

Note that the `decoder` is ran on the value found at the last key in the path, even if the last key is not found. This allows the `optional` decoder to succeed when appropriate.
Note that the `decoder` is ran on the value found at the last key in the path, even if the last key is not found. This allows the value to be `undefined` when appropriate.

```
const optionalDecoder = valueAt(['a', 'b', 'c'], optional(string()));
const decoder = valueAt(['a', 'b', 'c'], union(string(), constant(undefined)));

optionalDecoder.run({a: {b: {c: 'surprise!'}}})
decoder.run({a: {b: {c: 'surprise!'}}})
// => {ok: true, result: 'surprise!'}

optionalDecoder.run({a: {b: 'cats'}})
decoder.run({a: {b: 'cats'}})
// => {ok: false, error: {... at: 'input.a.b.c' message: 'expected an object, got "cats"'}

optionalDecoder.run({a: {b: {z: 1}}})
decoder.run({a: {b: {z: 1}}})
// => {ok: true, result: undefined}
```

Expand Down
144 changes: 144 additions & 0 deletions docs/classes/_decoder_.optionaldecoder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
[@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) > [OptionalDecoder](../classes/_decoder_.optionaldecoder.md)

# Class: OptionalDecoder

The `optional` decoder is given it's own type, the `OptionalDecoder` type, since it behaves differently from the other decoders. This decoder has no `run` method, so it can't be directly used to test a value. Instead, the `object` decoder accepts `optional` for decoding object properties that have been marked as optional with the `field?: value` notation.

## Type parameters
#### A
## Hierarchy

**OptionalDecoder**

## Index

### Properties

* [decode](_decoder_.optionaldecoder.md#decode)

### Methods

* [andThen](_decoder_.optionaldecoder.md#andthen)
* [map](_decoder_.optionaldecoder.md#map)
* [optional](_decoder_.optionaldecoder.md#optional)

---

## Properties

<a id="decode"></a>

### `<Private>` decode

**● decode**: *`function`*

#### Type declaration
▸(json: *`any`*): `Result.Result`< `A` &#124; `undefined`, `Partial`<[DecoderError](../interfaces/_decoder_.decodererror.md)>>

**Parameters:**

| Param | Type |
| ------ | ------ |
| json | `any` |

**Returns:** `Result.Result`< `A` &#124; `undefined`, `Partial`<[DecoderError](../interfaces/_decoder_.decodererror.md)>>

___

## Methods

<a id="andthen"></a>

### andThen

▸ **andThen**B(f: *`function`*): [OptionalDecoder](_decoder_.optionaldecoder.md)<`B`>

See `Decoder.prototype.andThen`. The function `f` is only executed if the optional decoder successfuly finds and decodes a value.

**Type parameters:**

#### B
**Parameters:**

| Param | Type |
| ------ | ------ |
| f | `function` |

**Returns:** [OptionalDecoder](_decoder_.optionaldecoder.md)<`B`>

___
<a id="map"></a>

### map

▸ **map**B(f: *`function`*): [OptionalDecoder](_decoder_.optionaldecoder.md)<`B`>

See `Decoder.prototype.map`. The function `f` is only executed if the optional decoder successfuly finds and decodes a value.

**Type parameters:**

#### B
**Parameters:**

| Param | Type |
| ------ | ------ |
| f | `function` |

**Returns:** [OptionalDecoder](_decoder_.optionaldecoder.md)<`B`>

___
<a id="optional"></a>

### `<Static>` optional

▸ **optional**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [OptionalDecoder](_decoder_.optionaldecoder.md)<`A`>

Decoder to designate that a property may not be present in an object. The behavior of `optional` is distinct from using `constant(undefined)` in that when the property is not found in the input, the key will not be present in the decoded value.

Example:

```
// type with explicit undefined property
interface Breakfast1 {
eggs: number;
withBacon: boolean | undefined;
}

// type with optional property
interface Breakfast2 {
eggs: number;
withBacon?: boolean;
}

// in the first case we can't use `optional`
breakfast1Decoder = object<Breakfast1>({
eggs: number(),
withBacon: union(boolean(), constant(undefined))
});

// in the second case we can
breakfast2Decoder = object<Breakfast2>({
eggs: number(),
withBacon: optional(boolean())
});

breakfast1Decoder.run({eggs: 12})
// => {ok: true, result: {eggs: 12, withBacon: undefined}}

breakfast2Decoder.run({eggs: 7})
// => {ok: true, result: {eggs: 7}}
```

**Type parameters:**

#### A
**Parameters:**

| Param | Type |
| ------ | ------ |
| decoder | [Decoder](_decoder_.decoder.md)<`A`> |

**Returns:** [OptionalDecoder](_decoder_.optionaldecoder.md)<`A`>

___

47 changes: 11 additions & 36 deletions docs/modules/_combinators_.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* [dict](_combinators_.md#dict)
* [fail](_combinators_.md#fail)
* [lazy](_combinators_.md#lazy)
* [object](_combinators_.md#object)
* [oneOf](_combinators_.md#oneof)
* [optional](_combinators_.md#optional)
* [succeed](_combinators_.md#succeed)
Expand All @@ -22,7 +23,6 @@
* [boolean](_combinators_.md#boolean)
* [constant](_combinators_.md#constant)
* [number](_combinators_.md#number)
* [object](_combinators_.md#object)
* [string](_combinators_.md#string)
* [union](_combinators_.md#union)

Expand Down Expand Up @@ -135,6 +135,15 @@ See `Decoder.lazy`

**Returns:** [Decoder](../classes/_decoder_.decoder.md)<`A`>

___
<a id="object"></a>

### `<Const>` object

**● object**: *[object](../classes/_decoder_.decoder.md#object)* = Decoder.object

See `Decoder.object`

___
<a id="oneof"></a>

Expand Down Expand Up @@ -163,24 +172,10 @@ ___

### `<Const>` optional

**● optional**: *`function`* = Decoder.optional
**● optional**: *[optional]()* = OptionalDecoder.optional

See `Decoder.optional`

#### Type declaration
▸A(decoder: *[Decoder](../classes/_decoder_.decoder.md)<`A`>*): [Decoder](../classes/_decoder_.decoder.md)< `A` &#124; `undefined`>

**Type parameters:**

#### A
**Parameters:**

| Param | Type |
| ------ | ------ |
| decoder | [Decoder](../classes/_decoder_.decoder.md)<`A`> |

**Returns:** [Decoder](../classes/_decoder_.decoder.md)< `A` &#124; `undefined`>

___
<a id="succeed"></a>

Expand Down Expand Up @@ -319,26 +314,6 @@ See `Decoder.number`

**Returns:** [Decoder](../classes/_decoder_.decoder.md)<`number`>

___
<a id="object"></a>

### object

▸ **object**A(decoders: *[DecoderObject](_decoder_.md#decoderobject)<`A`>*): [Decoder](../classes/_decoder_.decoder.md)<`A`>

See `Decoder.object`

**Type parameters:**

#### A
**Parameters:**

| Param | Type |
| ------ | ------ |
| decoders | [DecoderObject](_decoder_.md#decoderobject)<`A`> |

**Returns:** [Decoder](../classes/_decoder_.decoder.md)<`A`>

___
<a id="string"></a>

Expand Down
Loading