@@ -14,25 +14,44 @@ export interface DecoderError {
14
14
}
15
15
16
16
/**
17
- * Defines a mapped type over an interface `A`. `DecoderObject<A>` is an
18
- * interface that has all the keys or `A`, but each key's property type is
19
- * mapped to a decoder for that type. This type is used when creating decoders
20
- * for objects.
17
+ * Helper type with no semantic meaning, used as part of a trick in
18
+ * `DecoderObject` to distinguish between optional properties and properties
19
+ * that may have a value of undefined, but aren't optional.
20
+ */
21
+ type HideUndefined < T > = { } ;
22
+
23
+ /**
24
+ * Defines a mapped type over an interface `A`. This type is used when creating
25
+ * decoders for objects.
26
+ *
27
+ * `DecoderObject<A>` is an interface that has all the properties or `A`, but
28
+ * each property's type is mapped to a decoder for that type. If a property is
29
+ * required in `A`, the decoder type is `Decoder<proptype>`. If a property is
30
+ * optional in `A`, then that property is required in `DecoderObject<A>`, but
31
+ * the decoder type is `OptionalDecoder<proptype> | Decoder<proptype>`.
32
+ *
33
+ * The `OptionalDecoder` type is only returned by the `optional` decoder.
21
34
*
22
35
* Example:
23
36
* ```
24
- * interface X {
37
+ * interface ABC {
25
38
* a: boolean;
26
- * b: string;
39
+ * b?: string;
40
+ * c: number | undefined;
27
41
* }
28
42
*
29
- * const decoderObject: DecoderObject<X> = {
30
- * a: boolean(),
31
- * b: string()
43
+ * DecoderObject<ABC> === {
44
+ * a: Decoder<boolean>;
45
+ * b: OptionalDecoder<string> | Decoder<string>;
46
+ * c: Decoder<number | undefined>;
32
47
* }
33
48
* ```
34
49
*/
35
- export type DecoderObject < A > = { [ t in keyof A ] : Decoder < A [ t ] > } ;
50
+ export type DecoderObject < T > = {
51
+ [ P in keyof T ] -?: undefined extends { [ Q in keyof T ] : HideUndefined < T [ Q ] > } [ P ]
52
+ ? OptionalDecoder < Exclude < T [ P ] , undefined > > | Decoder < Exclude < T [ P ] , undefined > >
53
+ : Decoder < T [ P ] >
54
+ } ;
36
55
37
56
/**
38
57
* Type guard for `DecoderError`. One use case of the type guard is in the
@@ -112,6 +131,8 @@ const prependAt = (newAt: string, {at, ...rest}: Partial<DecoderError>): Partial
112
131
* things with a `Result` as with the decoder methods.
113
132
*/
114
133
export class Decoder < A > {
134
+ readonly _kind = 'Decoder' ;
135
+
115
136
/**
116
137
* The Decoder class constructor is kept private to separate the internal
117
138
* `decode` function from the external `run` function. The distinction
@@ -215,12 +236,13 @@ export class Decoder<A> {
215
236
* isBig: boolean;
216
237
* }
217
238
*
218
- * const bearDecoder1: Decoder<Bear> = object({
239
+ * const bearDecoder1 = object<Bear> ({
219
240
* kind: constant('bear'),
220
241
* isBig: boolean()
221
242
* });
222
- * // Type 'Decoder<{ kind: string; isBig: boolean; }>' is not assignable to
223
- * // type 'Decoder<Bear>'. Type 'string' is not assignable to type '"bear"'.
243
+ * // Types of property 'kind' are incompatible.
244
+ * // Type 'Decoder<string>' is not assignable to type 'Decoder<"bear">'.
245
+ * // Type 'string' is not assignable to type '"bear"'.
224
246
*
225
247
* const bearDecoder2: Decoder<Bear> = object({
226
248
* kind: constant<'bear'>('bear'),
@@ -280,15 +302,17 @@ export class Decoder<A> {
280
302
let obj : any = { } ;
281
303
for ( const key in decoders ) {
282
304
if ( decoders . hasOwnProperty ( key ) ) {
283
- const r = decoders [ key ] . decode ( json [ key ] ) ;
284
- if ( r . ok === true ) {
285
- // tslint:disable-next-line:strict-type-predicates
286
- if ( r . result !== undefined ) {
287
- obj [ key ] = r . result ;
288
- }
289
- } else if ( json [ key ] === undefined ) {
305
+ // hack: type as any to access the private `decode` method on OptionalDecoder
306
+ const decoder : any = decoders [ key ] ;
307
+ const r = decoder . decode ( json [ key ] ) ;
308
+ if (
309
+ ( r . ok === true && decoder . _kind === 'Decoder' ) ||
310
+ ( r . ok === true && decoder . _kind === 'OptionalDecoder' && r . result !== undefined )
311
+ ) {
312
+ obj [ key ] = r . result ;
313
+ } else if ( r . ok === false && json [ key ] === undefined ) {
290
314
return Result . err ( { message : `the key '${ key } ' is required but was not present` } ) ;
291
- } else {
315
+ } else if ( r . ok === false ) {
292
316
return Result . err ( prependAt ( `.${ key } ` , r . error ) ) ;
293
317
}
294
318
}
@@ -363,28 +387,6 @@ export class Decoder<A> {
363
387
}
364
388
} ) ;
365
389
366
- /**
367
- * Decoder for values that may be `undefined`. This is primarily helpful for
368
- * decoding interfaces with optional fields.
369
- *
370
- * Example:
371
- * ```
372
- * interface User {
373
- * id: number;
374
- * isOwner?: boolean;
375
- * }
376
- *
377
- * const decoder: Decoder<User> = object({
378
- * id: number(),
379
- * isOwner: optional(boolean())
380
- * });
381
- * ```
382
- */
383
- static optional = < A > ( decoder : Decoder < A > ) : Decoder < undefined | A > =>
384
- new Decoder < undefined | A > (
385
- ( json : any ) => ( json === undefined ? Result . ok ( undefined ) : decoder . decode ( json ) )
386
- ) ;
387
-
388
390
/**
389
391
* Decoder that attempts to run each decoder in `decoders` and either succeeds
390
392
* with the first successful decoder, or fails after all decoders have failed.
@@ -655,3 +657,88 @@ export class Decoder<A> {
655
657
Result . andThen ( value => f ( value ) . decode ( json ) , this . decode ( json ) )
656
658
) ;
657
659
}
660
+
661
+ /**
662
+ * The `optional` decoder is given it's own type, the `OptionalDecoder` type,
663
+ * since it behaves differently from the other decoders. This decoder has no
664
+ * `run` method, so it can't be directly used to test a value. Instead, the
665
+ * `object` decoder accepts `optional` for decoding object properties that have
666
+ * been marked as optional with the `field?: value` notation.
667
+ */
668
+ export class OptionalDecoder < A > {
669
+ readonly _kind = 'OptionalDecoder' ;
670
+
671
+ private constructor (
672
+ private decode : ( json : any ) => Result . Result < A | undefined , Partial < DecoderError > >
673
+ ) { }
674
+
675
+ /**
676
+ * Decoder to designate that a property may not be present in an object. The
677
+ * behavior of `optional` is distinct from using `constant(undefined)` in
678
+ * that when the property is not found in the input, the key will not be
679
+ * present in the decoded value.
680
+ *
681
+ * Example:
682
+ * ```
683
+ * // type with explicit undefined property
684
+ * interface Breakfast1 {
685
+ * eggs: number;
686
+ * withBacon: boolean | undefined;
687
+ * }
688
+ *
689
+ * // type with optional property
690
+ * interface Breakfast2 {
691
+ * eggs: number;
692
+ * withBacon?: boolean;
693
+ * }
694
+ *
695
+ * // in the first case we can't use `optional`
696
+ * breakfast1Decoder = object<Breakfast1>({
697
+ * eggs: number(),
698
+ * withBacon: union(boolean(), constant(undefined))
699
+ * });
700
+ *
701
+ * // in the second case we can
702
+ * breakfast2Decoder = object<Breakfast2>({
703
+ * eggs: number(),
704
+ * withBacon: optional(boolean())
705
+ * });
706
+ *
707
+ * breakfast1Decoder.run({eggs: 12})
708
+ * // => {ok: true, result: {eggs: 12, withBacon: undefined}}
709
+ *
710
+ * breakfast2Decoder.run({eggs: 7})
711
+ * // => {ok: true, result: {eggs: 7}}
712
+ * ```
713
+ */
714
+ static optional = < A > ( decoder : Decoder < A > ) : OptionalDecoder < A > =>
715
+ new OptionalDecoder (
716
+ // hack: type decoder as any to access the private `decode` method on Decoder
717
+ ( json : any ) => ( json === undefined ? Result . ok ( undefined ) : ( decoder as any ) . decode ( json ) )
718
+ ) ;
719
+
720
+ /**
721
+ * See `Decoder.prototype.map`. The function `f` is only executed if the
722
+ * optional decoder successfuly finds and decodes a value.
723
+ */
724
+ map = < B > ( f : ( value : A ) => B ) : OptionalDecoder < B > =>
725
+ new OptionalDecoder < B > ( ( json : any ) =>
726
+ Result . map (
727
+ ( value : A | undefined ) => ( value === undefined ? undefined : f ( value ) ) ,
728
+ this . decode ( json )
729
+ )
730
+ ) ;
731
+
732
+ /**
733
+ * See `Decoder.prototype.andThen`. The function `f` is only executed if the
734
+ * optional decoder successfuly finds and decodes a value.
735
+ */
736
+ andThen = < B > ( f : ( value : A ) => Decoder < B > ) : OptionalDecoder < B > =>
737
+ new OptionalDecoder < B > ( ( json : any ) =>
738
+ Result . andThen (
739
+ ( value : A | undefined ) =>
740
+ value === undefined ? Result . ok ( undefined ) : ( f ( value ) as any ) . decode ( json ) ,
741
+ this . decode ( json )
742
+ )
743
+ ) ;
744
+ }
0 commit comments