effect
effect copied to clipboard
Defaulting nested partial struct properties
🐛 Bug report
Current Behavior
The current behaviour of withDefault works on a single level of S.optional as shown in the docs example:
const schema1 = S.struct({
a: S.optional(S.number).withDefault(() => 0),
});
S.parseEither(schema1)({}); // => { _tag: 'Right', right: { a: 0 } }
But when you have a nested optional struct that has a withDefault, the default doesn't apply.
const schema2 = S.struct({
a: S.optional(
S.struct({
b: S.optional(S.number).withDefault(() => 0),
}),
),
});
S.parseEither(schema2)({}); // => { _tag: 'Right', right: {} }
Expected behavior
I would expect that the below would happen:
const schema3 = S.struct({
a: S.optional(
S.struct({
b: S.optional(S.number).withDefault(() => 0),
}),
),
});
S.parseEither(schema3)({}); // => { _tag: 'Right', right: { a: { b: 0 } } }
Maybe this is intentional but if so I found it counter intuitive to having a default for a property.
Thanks for your time!
Your environment
Which versions of @effect/schema are affected by this issue? Did this work in previous versions of @effect/schema?
| Software | Version(s) |
|---|---|
| @effect/schema | 0.33.1 |
| TypeScript | 5.1.6 |
This is intentional: a is optional therefore {} is a legal value (and is returned as an ok result).
You may want to add a default to a as well:
import * as S from '@effect/schema/Schema'
const schema3 = S.struct({
a: S.optional(
S.struct({
b: S.optional(S.number).withDefault(() => 0)
})
).withDefault(() => ({ b: 0 }))
})
console.log(S.parseEither(schema3)({})) // Output: { _tag: 'Right', right: { a: { b: 0 } } }
console.log(S.parseEither(schema3)({ a: {} })) // Output: { _tag: 'Right', right: { a: { b: 0 } } }
console.log(S.parseEither(schema3)({ a: { b: 1 } })) // Output: { _tag: 'Right', right: { a: { b: 1 } } }
In the case that you had a very complex type:
import * as S from '@effect/schema/Schema';
const Attributes = S.struct({
/* Large schema with many optional values, some with defaults. */
});
const Product = S.struct({
name: S.string,
attributes: S.optional(Attributes),
});
Is there a way today to get the default values for a schema? If not, could this be added?
const Product = S.struct({
name: S.string,
attributes: S.optional(Attributes).withDefaults(),
});
const Product = S.struct({
name: S.string,
attributes: S.defaultedOptional(Attributes)
});
Update: now that we have constructors (make) the simplest solution is setting the default using them:
import { Schema as S } from "@effect/schema"
const Foo = S.Struct({
a: S.Number.pipe(S.optional({ default: () => 1 })),
b: S.String.pipe(S.optional({ default: () => "hello" }))
})
const Bar = S.Struct({
foo: Foo.pipe(S.optional({ default: () => Foo.make({}) }))
})
console.log(S.decodeUnknownEither(Bar)({})) // { _id: 'Either', _tag: 'Right', right: { foo: { a: 1, b: 'hello' } } }
console.log(S.decodeUnknownEither(Bar)({ foo: undefined })) // { _id: 'Either', _tag: 'Right', right: { foo: { a: 1, b: 'hello' } } }