diff --git a/README.md b/README.md index 893df5b..d084cab 100644 --- a/README.md +++ b/README.md @@ -138,3 +138,28 @@ import { Decoder, array, field, number, string } from "json-decode"; array(number)([1, 2, 3]); // [1, 2, 3] array(number)([1, 2, '3']); // throws a DecodeError ``` + +### Decoding a TypeScript enum + +```typescript +import { enumerator } from "json-decode"; + +enum Choice { + carrot = 'carrot', + stick = 'stick' +} + +enumerator(Choice)('carrot'); // Choice.carrot +enumerator(Choice)('stick'); // Choice.stick +enumerator(Choice)('banana'); // throws a DecodeError + +enum Fruits { + apple, + banana, + pear, +} + +enumerator(Fruits)(0); // Fruits.apple +enumerator(Fruits)(1) // Fruits.banana +enumerator(Fruits)(3) // throws a DecodeError +``` diff --git a/src/index.d.ts b/src/index.d.ts index a2ce781..c552db5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -25,3 +25,5 @@ export function nullable(decoder: Decoder): Decoder; export function optional(decoder: Decoder): Decoder; export const string: Decoder; + +export const enumerator: (enumObject: T) => Decoder; diff --git a/src/index.ts b/src/index.ts index 8fad885..49ef372 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,11 +4,12 @@ export { bool, Decoder, DecoderError, + enumerator, int, field, number, float, nullable, optional, - string + string, } from './lib/json-decode' diff --git a/src/lib/json-decode.spec.ts b/src/lib/json-decode.spec.ts index 85246a2..b8ccf46 100644 --- a/src/lib/json-decode.spec.ts +++ b/src/lib/json-decode.spec.ts @@ -4,15 +4,15 @@ import { bigint, bool, Decoder, - DecoderError, + DecoderError, enumerator, field, float, int, nullable, number, optional, - string, -} from './json-decode'; + string +} from "./json-decode"; describe('bool', () => { it('decodes a boolean', () => { @@ -125,6 +125,55 @@ describe('nullable', () => { }); }); +describe('enumerator', () => { + + describe('when the enum is a string enum', () => { + enum Choice { + carrot = 'carrot', + stick = 'stick' + } + + it('decodes the enum', () => { + expect(enumerator(Choice)('carrot')).toEqual(Choice.carrot); + }); + + it('throws when the value is not a member of the enum', () => { + expect(() => enumerator(Choice)('banana')).toThrowError(DecoderError); + }); + }); + + describe('when the enum is a numeric enum', () => { + enum Choice { + carrot = 0, + stick = 1 + } + + it('decodes the enum', () => { + expect(enumerator(Choice)(0)).toEqual(Choice.carrot); + }); + + it('throws when the value is not a member of the enum', () => { + expect(() => enumerator(Choice)('banana')).toThrowError(DecoderError); + }); + }); + + + describe('when the enum has no values assigned', () => { + enum Choice { + carrot , + stick + } + + it('decodes the enum using the value', () => { + expect(enumerator(Choice)(1)).toEqual(Choice.stick); + }); + + it('throws when the value is not a member of the enum', () => { + expect(() => enumerator(Choice)(3)).toThrowError(DecoderError); + }); + }); +}) + describe('decoding a complex object', () => { type Blob = { name: string; diff --git a/src/lib/json-decode.ts b/src/lib/json-decode.ts index cc44752..1e36cd5 100644 --- a/src/lib/json-decode.ts +++ b/src/lib/json-decode.ts @@ -114,3 +114,16 @@ export const nullable = (decoder: Decoder): Decoder => (json) => json === null ? json : decoder(json); + +export const enumerator = (enumType: T) => (json: unknown): T[keyof T] => { + const entries = Object.entries(enumType as any); + const entry = entries.find(([, v]) => v === json); + if (entry === undefined) { + throw new DecoderError(`Expected enum value, got ${JSON.stringify(json)}`); + } + const value = entry[1]; + if (value === undefined) { + throw new DecoderError(`Expected enum value, got ${JSON.stringify(json)}`); + } + return value as T[keyof T]; +} \ No newline at end of file