Hi!
Thanks so much for this library! It's a great help with keeping my data consistent across my app stack.
I am working on creating schemas to represent objects in a Firebase Realtime Database, which doesn't really use arrays for lists of data, but rather dictionaries using uuids as keys. I'm trying to create a simple way of representing these hash-mapped lists of data in my schemas. I've been working with the suggestions brought up in https://github.com/jquense/yup/issues/130 but have been unsuccessful in getting it to correctly unpack the values.
Here is my current code:
export interface Thread extends FirebaseObject {
uuid: string;
unread?: number;
last: Message;
members: { [uuid: string]: string };
messages: { [uuid: string]: Message };
threadName?: string;
}
export const threadSchema: yup.Schema<Thread> = yup.object().shape({
uuid: yup.string().required(),
unread: yup
.number()
.positive()
.integer(),
last: messageSchema,
members: yup.lazy(obj => {
return yup.object(
Object.keys(obj).map(key => {
return yup.object({
[`${key}`]: yup.string(),
});
})
);
}),
messages: yup.lazy(obj =>
yup.object(
Object.keys(obj).map(key => {
return yup.object({
[`${key}`]: messageSchema,
});
})
)
),
threadName: yup.string(),
});
Here is a sample "Thread" object:
{
"uuid": "demo",
"unread": 0,
"last": { ... },
"members": {
"someUniqueId": "Hank",
"anotherUniqueId": "Henry"
},
"messages": {
"messageID1": { ... }
"messageID2": { ... }
},
"threadName": "Hank and Henry"
}
I'm closer, but still not quite there. I was essentially returning an array of key/value pairs instead of an object with the proper keys. After changing the underlying logic I have created a generic function that makes the code reusable below. However, I'm having trouble with the values being type-safe, please let me know if you have a more elegant solution or if there is a better way to handle hash-mapped data.
export const threadSchema: yup.ObjectSchema<Thread> = yup.object().shape({
uuid: yup.string().required(),
unread: yup
.number()
.positive()
.integer(),
last: messageSchema,
members: firebaseList<string>(yup.string()),
messages: firebaseList<Message>(messageSchema),
threadName: yup.string(),
});
export interface FirebaseList<T> {
[uuid: string]: T;
}
export function firebaseList<T>(schema: yup.Schema<T>): yup.Schema<FirebaseList<T>> {
return yup.lazy<FirebaseList<T>>(obj => {
let ret: { [uuid: string]: yup.Schema<T> } = {};
for (let key in obj) {
ret[key] = schema;
}
return yup.object(ret);
});
}
I too need this. Lazy seems like a weird way of doing things. If someone is willing to do it, an API like that of Joi seems appropriate: https://github.com/hapijs/joi/blob/master/API.md#objectpatternpattern-schema-options
yup.object({}).pattern(yup.string(), yup.object({someField: yup.string()})
For performance reasons I need another approach from lazy. Validating thousands of records, deeply nested, and yup is currently doing it in roughly 10 seconds.
I'd like to see the approach suggested by scottmas.
Need something like that too, but I don't care about keys, so I've figured a quick and dirty way...
const schema = yup
.array()
.transform((_, orig) => Object.values(orig))
.of(objectSchema)
Hey @FredyC that's a great suggestion. I think that would help us out a lot. While I would like to see similar solution to what Joi has, this will do it for us meanwhile. Thanks!
@FredyC this codesandbox link is broken
@CleitinIF What do you mean? It's throwing an error on purpose to show failed validation 馃槅
really, sorry 馃槄
I found I was able to do this by creating a schema of type yup.mixed()
Hey folks, since this is a commonly requested thing, I threw together a simple version of how you might implement this as a proper schema. Edit to suit your needs:
https://repl.it/@jquense/Map-schema
Thank you @jquense, much appreciated! Any chance it will make it into the next version of Yup?
How are you guys using this, simply adding the Map-schema example in a file in your project and then importing this into your schema's? (until it is supported by Yup natively)
The MapSchema from https://github.com/jquense/yup/issues/524#issuecomment-559116932 throws a TypeError instead of a ValidationError if it can't cast a value. Is this the intended behavior?
const schema = MapSchema(
string().min(3),
object({
name: string().required().nullable(),
age: number().required()
})
)
console.log(
schema.validateSync({
'id1': { name: 'john', age: 'x' },
'id2': { name: null }
})
)
Could not make Map-schema work in typescript.
Here is a workaround which works in our case:
export const approveInputSchema = yup.object().shape({
responseType: yup.string().required(),
requestId: yup.string().required(),
// map hack until it is supported:
scopeSelection: yup.object(),
state: yup.string().required(),
approval: yup.string().oneOf(['approved', 'denied']).required(),
}).noUnknown();
type ApproveInputInferred = yup.InferType<typeof approveInputSchema>;
interface ScopeSelection {
[ scope: string ]: boolean;
}
type Approval = 'approved' | 'denied';
export interface ApproveInput extends ApproveInputInferred {
scopeSelection: ScopeSelection;
approval: Approval;
}
It is missing scopeSelection object shape run-time validation, but validates correctly via typescript at compile-time.
Also, notice that type of approval is inferred as string - not as 'approved' | 'denied' (possibly another issue?).
Here is a working typescript'ish version of https://repl.it/@jquense/Map-schema:
https://repl.it/repls/MiserableCluelessTheory
It should support InferType out of the box.
@jquense any chance you could add an official implementation in the library? It seems like a core component is missing.
yup.lazy(details => {
return yup.object(
Object.fromEntries(
Object.keys(details).map(key => [
key,
yup.object({ azimuth: yup.number().required() }),
])
)
);
}),
Caution: the lazy option will be extremely slow with big datasets.
Hey folks, since this is a commonly requested thing, I threw together a simple version of how you might implement this as a proper schema. Edit to suit your needs:
https://repl.it/@jquense/Map-schema
@jquense In respect of this comment, I think code here is in much need of some nice upgrade to match the latest version of yup. I see that it's using 0.27, however, it's plainly not working with 0.32.8.
I tried to make it work myself but failed miserably. First of all, runValidations appears to be no longer available as an export. After doing some looking around, I concluded that runTests should be used in its place, and also the validations property it receives in line 86 should be renamed to tests.
That didn't stop there as I started getting 'Cannot read property "length" of undefined':
/.../core/node_modules/yup/lib/schema.js:164
if (schema.conditions.length) {
^
TypeError: Cannot read property 'length' of undefined
at MapSchema.resolve (/.../core/node_modules/yup/lib/schema.js:164:27)
at MapSchema.validateSync (/.../core/node_modules/yup/lib/schema.js:273:23)
at Object.<anonymous> (/.../core/server/constants/esg-questionnaires/validators/MapSchema.js:105:10)
I noticed that apparently, any of the properties initialized in BaseSchema constructor aren't so when using MapSchema. I fiddled around a bit with inheritance, changing things, made MapSchema inherit from BaseSchema, calling super here and there, but it was all completely fruitless. I even copied all the properties from BaseSchema's constructor into MapSchema's one to see what would happen and from that point on, I started getting Cannot read property 'XXX' of undefined errors, each time I fixed, a different one would pop up next.
I'm very much in need of this, so it'd be awesome if you or anyone could take the time to fine-tune and ready this up. I can sure be of help too if needed. I'm guessing that for anyone with even a moderate acquaintance with the code here, this shouldn't present much trouble.
Same requirements on my side, lazy does work but is clearly inefficient. Would be great to have this feature natively supported by the library.
Edit: my solution was to abandon yup in favor of joi.
Most helpful comment
@jquense any chance you could add an official implementation in the library? It seems like a core component is missing.