Skip to content

Object schema breaking nested pipe output type #1095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
buzlo opened this issue Mar 21, 2025 · 5 comments
Open

Object schema breaking nested pipe output type #1095

buzlo opened this issue Mar 21, 2025 · 5 comments
Assignees
Labels
intended The behavior is expected

Comments

@buzlo
Copy link

buzlo commented Mar 21, 2025

Hey again, I've noticed a weird behavior with the latest update.

Here is an example snippet of transform pipe nested inside an object schema.

const transformPipe = pipe(optional(string()), transform((input) => input ?? 'fallback'))
const objectSchema = object({
	pipeField: transformPipe,
	noPipeField: string(),
})

type PipeOutputType = InferOutput<typeof transformPipe> // string
type ObjectOutputType = InferOutput<(typeof objectSchema)> //  { pipeField?: string | undefined;  noPipeField: string; }

As you can see, the output type of that pipe is clearly string, but when it is nested inside an object schema it is still for some reason inferred as optional, while the type of a field that is directly assigned string() schema is inferred correctly.

This is most likely a bug, could you please look into it?
Thanks!

@fabian-hiller
Copy link
Owner

fabian-hiller commented Mar 21, 2025

Thank you for reaching out! This is not a bug. We expanded the validation capabilities of object to be able to distinguish between undefined properties and missing properties. I recommend to change your code to:

const Schema = v.optional(v.string(), 'default');

Feel free to reach out with any question you have. I am happy to explain what's happening under the hood.

@fabian-hiller fabian-hiller self-assigned this Mar 21, 2025
@fabian-hiller fabian-hiller added the intended The behavior is expected label Mar 21, 2025
@buzlo
Copy link
Author

buzlo commented Mar 23, 2025

May I ask you to elaborate?
This behavior still seems kind of unexpected to me: the input for transformPipe is indeed optional(string), which can be undefined or missing, but the output is strictly string.
The output could also theoretically be another completely unrelated, non-nullish type, the result of some internal transform logic from a string/undefined/missing field to whatever new type that might be.
The point is: even if the input for this transform action might be undefined or missing, its output is certainly not, that's why it doesn't seem right to me.
Could you please explain? Thanks!

@fabian-hiller
Copy link
Owner

Yes, I can explain it. optional signals to object that it is ok if the property is missing. If this is the case, as we distinguish between missing and undefined properties, the schema of the property is not executed. We chose to implement it this way because there are drawbacks in a few edge cases when we would execute the schema of the property. But by providing a default value (the second argument of optional), you tell object to execute the schema and apply the default value. There is a blog post a GitHub discussion and few PRs with more context.

@throrin19
Copy link

I don't know if my problem is the same but I try to migrate to 1.0.0 and I have a problem with nullish in object field.

Before 1.0.0 if I made this :

const schema = object({
    a: nullish(string(), 'default'),
});

const result = parse(schema, {}}, {
            abortEarly: true,
            abortPipeEarly: true,
        });

// { a: 'default'}

After 1.0.0 I have an error :

const schema = object({
    a: nullish(string(), 'default'),
});

const result = parse(schema, {}}, {
            abortEarly: true,
            abortPipeEarly: true,
        });

// Invalid key: Expected \\"a\\" but received undefined

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
intended The behavior is expected
Projects
None yet
Development

No branches or pull requests

3 participants