diff --git a/src/index.ts b/src/index.ts index d087f55..a0d82ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,7 +102,7 @@ export const Parse = { throw MismatchError("NaN", val); }, - instanceOf(classDef: { new (): T }): (val: unknown) => T { + instanceOf(classDef: { new(): T }): (val: unknown) => T { return (val: unknown) => { if (val instanceof classDef) { return val; @@ -149,11 +149,7 @@ export const Parse = { const result = {} as { [K in keyof T]: ReturnType }; for (const key in parsers) { - if (!(key in val)) { - throw new ParseError(`Missing prop: ${key}`, shapeName); - } - - const value = (val as Record)[key]; + const value = (val as Record)[key] ?? undefined; try { // biome-ignore lint/style/noNonNullAssertion: Logically guaranteed diff --git a/src/test/parse.test.ts b/src/test/parse.test.ts index 659e91f..5878a2f 100644 --- a/src/test/parse.test.ts +++ b/src/test/parse.test.ts @@ -140,6 +140,33 @@ describe(Parse.shape, () => { const shape = { name: Parse.string, age: Parse.number }; expect(() => Parse.shape(shape)(obj)).toThrow(ParseError); }); + + test("allows optional object entries", () => { + const parseThing = Parse.shape({ + name: Parse.string, + another: Parse.optional(Parse.string), + }); + + const result = parseThing({ + name: "hello", + }); + + expect(result).toEqual({ + name: "hello", + another: undefined, + }); + }); + + test("throws when a required value is missing", () => { + const parseThing = Parse.shape({ + name: Parse.string, + another: Parse.optional(Parse.string), + }); + + expect(() => { + parseThing({ another: "hello" }); + }).toThrow(ParseError); + }); }); describe(Parse.enum, () => { @@ -197,7 +224,7 @@ describe(Parse.nan, () => { }); }); -class TestClass {} +class TestClass { } describe(Parse.instanceOf, () => { test("Parses instances of a class", () => { @@ -214,7 +241,7 @@ describe(Parse.instanceOf, () => { test("Throws an error when parsing an instance of a different class", () => { const testClass = new TestClass(); - expect(() => Parse.instanceOf(class AnotherClass {})(testClass)).toThrow( + expect(() => Parse.instanceOf(class AnotherClass { })(testClass)).toThrow( ParseError, ); });