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