@@ -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,7 @@ 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' ;
115
135
/**
116
136
* The Decoder class constructor is kept private to separate the internal
117
137
* `decode` function from the external `run` function. The distinction
@@ -280,15 +300,16 @@ export class Decoder<A> {
280
300
let obj : any = { } ;
281
301
for ( const key in decoders ) {
282
302
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 ) {
303
+ const decoder : any = decoders [ key ] ;
304
+ const r = decoder . decode ( json [ key ] ) ;
305
+ if (
306
+ ( r . ok === true && decoder . _kind === 'Decoder' ) ||
307
+ ( r . ok === true && decoder . _kind === 'OptionalDecoder' && r . result !== undefined )
308
+ ) {
309
+ obj [ key ] = r . result ;
310
+ } else if ( r . ok === false && json [ key ] === undefined ) {
290
311
return Result . err ( { message : `the key '${ key } ' is required but was not present` } ) ;
291
- } else {
312
+ } else if ( r . ok === false ) {
292
313
return Result . err ( prependAt ( `.${ key } ` , r . error ) ) ;
293
314
}
294
315
}
@@ -363,28 +384,6 @@ export class Decoder<A> {
363
384
}
364
385
} ) ;
365
386
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
387
/**
389
388
* Decoder that attempts to run each decoder in `decoders` and either succeeds
390
389
* with the first successful decoder, or fails after all decoders have failed.
@@ -655,3 +654,33 @@ export class Decoder<A> {
655
654
Result . andThen ( value => f ( value ) . decode ( json ) , this . decode ( json ) )
656
655
) ;
657
656
}
657
+
658
+ export class OptionalDecoder < A > {
659
+ readonly _kind = 'OptionalDecoder' ;
660
+
661
+ private constructor (
662
+ private decode : ( json : any ) => Result . Result < A | undefined , Partial < DecoderError > >
663
+ ) { }
664
+
665
+ static optional = < A > ( decoder : Decoder < A > ) : OptionalDecoder < A > =>
666
+ new OptionalDecoder (
667
+ ( json : any ) => ( json === undefined ? Result . ok ( undefined ) : ( decoder as any ) . decode ( json ) )
668
+ ) ;
669
+
670
+ map = < B > ( f : ( value : A ) => B ) : OptionalDecoder < B > =>
671
+ new OptionalDecoder < B > ( ( json : any ) =>
672
+ Result . map (
673
+ ( value : A | undefined ) => ( value === undefined ? undefined : f ( value ) ) ,
674
+ this . decode ( json )
675
+ )
676
+ ) ;
677
+
678
+ andThen = < B > ( f : ( value : A ) => Decoder < B > ) : OptionalDecoder < B > =>
679
+ new OptionalDecoder < B > ( ( json : any ) =>
680
+ Result . andThen (
681
+ ( value : A | undefined ) =>
682
+ value === undefined ? Result . ok ( undefined ) : ( f ( value ) as any ) . decode ( json ) ,
683
+ this . decode ( json )
684
+ )
685
+ ) ;
686
+ }
0 commit comments