Skip to content

Commit

Permalink
Add support for decoding non-const enums. (#25)
Browse files Browse the repository at this point in the history
* Add support for decoding non-const enums.

* Add documentation, set target back to es5, bump version, and minor cleanups.
  • Loading branch information
rhofour committed Sep 24, 2020
1 parent 3203af0 commit f61ddb5
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 2 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,37 @@ JsonDecoder.boolean.decode(true); // Ok<boolean>({value: true})
JsonDecoder.boolean.decode(null); // Err({error: 'null is not a valid boolean'})
```

### JsonDecoder.enumeration

> `enumeration<e>(enumObj: object, decoderName: string): Decoder<e>`
Creates a decoder for a (non-const) enum.

#### @param `enumObj: object`

The enum object to use for decoding. This doesn't exist for const enums.

#### @param `decoderName: string`

Type of the object we are decoding. i.e. `User`. It is used to generate meaningful decoding error messages.

#### Basic example

```ts
enum ExampleEnum {
X = 1,
Y, /* 2 */
Z = 'foo',
}

const exampleEnumDecoder = JsonDecoder.enumeration<ExampleEnum>(
ExampleEnum, 'ExampleEnum');

exampleEnumDecoder.decode(1); // Ok<ExampleEnum>({value: 1})
exampleEnumDecoder.decode(ExampleEnum.Y); // Ok<ExampleEnum>({value: 2})
exampleEnumDecoder.decode(3); // Err({error: '<ExampleEnum> decoder failed at value "3" which is not in the enum'})
```

### JsonDecoder.object

> `object<a>(decoders: DecoderObject<a>, decoderName: string, keyMap?: DecoderObjectKeyMap<a>): Decoder<a>`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts.data.json",
"version": "1.2.0",
"version": "1.3.0",
"description": "A JSON decoding library for Typescript",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
48 changes: 48 additions & 0 deletions src/json-decoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,54 @@ describe('json-decoder', () => {
});
});

// enumeration
describe('enumeration', () => {
enum IntEnum {
A,
B,
C,
}
enum OddlyOrderedIntEnum {
A = 2,
B = 8,
C = -3,
}
enum HeterogeneousEnum {
X = 1,
Y, /* 2 */
Z = 'foo',
}
it('should decode when the value is in the enum', () => {
expectOkWithValue(JsonDecoder.enumeration<IntEnum>(IntEnum, 'IntEnum').decode(1),
IntEnum.B /* 1 */);
expectOkWithValue(JsonDecoder.enumeration<OddlyOrderedIntEnum>(
OddlyOrderedIntEnum, 'OddlyOrderedIntEnum').decode(-3),
OddlyOrderedIntEnum.C /* -3 */);
expectOkWithValue(JsonDecoder.enumeration<HeterogeneousEnum>(
HeterogeneousEnum, 'HeterogeneousEnum').decode(2),
HeterogeneousEnum.Y /* 2 */);
expectOkWithValue(JsonDecoder.enumeration<HeterogeneousEnum>(
HeterogeneousEnum, 'HeterogeneousEnum').decode('foo'),
HeterogeneousEnum.Z /* 'foo' */);
});
it('should fail when the value is not in the enum', () => {
expectErrWithMsg(
JsonDecoder.enumeration<IntEnum>(IntEnum, 'IntEnum').decode(3),
$JsonDecoderErrors.enumValueError('IntEnum', 3)
);
expectErrWithMsg(
JsonDecoder.enumeration<IntEnum>(
OddlyOrderedIntEnum, 'OddlyOrderedIntEnum').decode(3),
$JsonDecoderErrors.enumValueError('OddlyOrderedIntEnum', 3)
);
expectErrWithMsg(
JsonDecoder.enumeration<HeterogeneousEnum>(
HeterogeneousEnum, 'HeterogeneousEnum').decode(0),
$JsonDecoderErrors.enumValueError('HeterogeneousEnum', 0)
);
});
});

// failover
describe('failover (on failure provide a default value)', () => {
it('should decode a value when value is provided', () => {
Expand Down
25 changes: 25 additions & 0 deletions src/json-decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ export namespace JsonDecoder {
}
});

/**
* Decode for `enumeration`.
*
* @param enumObj The enum object to use for decoding. Must not be a const enum.
* @param decoderName How to display the name of the object being decoded in errors.
*/
export function enumeration<e>(
enumObj: object,
decoderName: string
): Decoder<e> {
return new Decoder<e>((json: any) => {
const enumValue = Object.values(enumObj).find((x: any) => x === json);
if (enumValue) {
return ok<e>(enumValue);
}
return err<e>($JsonDecoderErrors.enumValueError(decoderName, json));
});
}

export type DecoderObject<a> = { [p in keyof Required<a>]: Decoder<a[p]> };
export type DecoderObjectKeyMap<a> = { [p in keyof a]?: string };

Expand Down Expand Up @@ -584,6 +603,12 @@ export namespace $JsonDecoderErrors {
json
)} can't be decoded with any of the provided oneOf decoders`;

export const enumValueError = (
decoderName: string,
invalidValue: any,
): string =>
`<${decoderName}> decoder failed at value "${invalidValue}" which is not in the enum`;

export const objectError = (
decoderName: string,
key: string,
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"outDir": "./dist",
"strict": true,
"lib": [
"es2016",
"ES2017",
"dom"
]
},
Expand Down

0 comments on commit f61ddb5

Please sign in to comment.