Implementing a 'filter' option to choose which 'fields' you wish to validate or to be returned from parsing?
codinsonn opened this issue · comments
Hi, I've been playing around with this solution in combination with Mongoose and have been loving it so far.
One thing I'm missing though is the ability to choose which fields you either:
- ... want back after parsing (something similar to how mongoose does it with their fields filter)
- ... wish to validate (for edge cases when validating certain fields isn't necessary)
Is a way to implement this functionality already by any chance?
Thanks in advance and congrats on creating this awesome package!
You can modify parse
and validate
methods by extending the schema. For example:
const schema = require('schm')
const omit = require('lodash/omit')
const ignore = (...params) => prevSchema => prevSchema.merge({
parse(values) {
return prevSchema.parse(omit(values, params))
},
validate(values) {
return prevSchema.validate(omit(values, params))
}
})
const mySchema = schema(
{
foo: String,
bar: String,
},
ignore('foo'),
)
Does it work for you?
Aha, I'd already gotten a similar solution working based on lodash/pick 👍
parseWithOptions.js
import cherryPick from './cherryPick';
module.exports = (obj, schema, options = {}) => {
console.log('-i- [parse:7] Parsing obj:', options);
// Parse obj
let _obj = schema.parse(obj);
// Cherrypick props
if (options.fields || options.omit || options.removeFalsy || options.removeNull || options.removeUndefined) {
cherryPick(_obj, options);
}
// Return object
console.log('-i- [parse:23] Returning parsed obj:', _obj);
return _obj;
};
Where cherryPick is a helper function that uses lodash/pick and lodash/omit to cherrypick values based on options.
cherryPick.js
import _ from 'lodash';
module.exports = (obj, options = {}) => {
// Defaults
fields = _.defaultTo(options.fields, '');
omit = _.defaultTo(options.omit, '');
removeFalsy = _.defaultTo(options.removeFalsy, false);
removeNull = _.defaultTo(options.removeNull, false);
removeUndefined = _.defaultTo(options.removeUndefined, false);
removeNaN = _.defaultTo(options.removeNaN, false);
// Cherrypick values
if (fields !== '') obj = _.pick(obj, fields.split(' ')); // only includes supplied values
if (omit !== '') obj = _.omit(obj, omit.split(' ')); // deletes supplied values
// Omit falsy values
if (removeFalsy) obj = _.pickBy(obj);
if (removeNull) obj = _.omitBy(obj, _.isNull);
if (removeUndefined) obj = _.omitBy(obj, _.isUndefined);
if (removeNaN) obj = _.omitBy(obj, _.isNaN);
// Return object
return obj;
};
But I didn't know I could simply extend it like this without needing the 'schm-methods' package as well, so this will definitely help me further, thanks!
I'm still a bit confused as to how extension like in the example you provided actually works.
Does this add ignore as a callable method on the schema? (with variable arguments)
In the end result I'm looking for I'd like to be able to define extensions and call them like so:
// Before parsing, as a kind of plugin
MySchema.validateWithOptions(obj, { omit: "name.middle" }); // Ignores name.middle in validation
MySchema.parseWithOptions(obj, { fields: "name.first name.last foo bar" }); // Cherrypick fields
// After parsing (with 'schm-methods'?)
parsedUser.validateWithOptions(parsedUser, { fields: "name.first name.last foo bar" });
Is this something that's possible with your proposed solution?
Also, can methods defined by 'schm-methods' reference the schema they're called on by way of using 'this'? (lexical binding of this could become a problem, I guess)
Because if that's the case I could then just pass the helper/util I already wrote as method like so:
const MySchema = schema(
{ ... },
methods(
parseWithOptions: (obj, options = {}) => parseWithOptions(obj, this, options),
validateWithOptions: (obj, options = {}) => validateWithOptions(obj, this, options),
)
);
Would that be possible? Or is there another way to add a "plugin" to a schema?
Perhaps being able to chain a plugin and/or method is a good option for this? (#32)
const MySchema = schema({ ... })
.plugin(parseWithOptions)
.plugin(validateWithOptions)
.method(validateWithOptions);
@ThorrStevens
This is the interface of schema
:
schema(...args: Object | Schema | Function)
Where Object
is the literal object, Schema
is another schema and Function
some function that receives the previous schema as the only argument.
Passing a literal object to schema
is the same as using group
(when you pass a literal object, it's passed to group
under the hood):
// these are the same
schema({ foo: String })
schema(group({ foo: String }))
The implementation of group
is something like this:
const group = params => prevSchema => prevSchema.merge({ params })
The result of prevSchema.merge({ params })
is the schema itself, which is an object with this structure:
{
params: Object,
parse: Function,
validate: Function,
parsers: Object,
validators: Object,
merge: Function,
}
That said, if you want to call MySchema.validateWithOptions
you need to add the method to the object above. And you can do that the same way that group
added params
:
const withValidateWithOptions = prevSchema => prevSchema.merge({
validateWithOptions(values, options) {
return validateWithOptions(values, this, options)
}
})
const mySchema = schema({ ... }, withValidateWithOptions)
mySchema.validateWithOptions({ ... }, options)
To clarify, schm-methods
adds methods to the parsed object:
const mySchema = schema(
{ ... },
methods({
foo: () => {}
})
)
const parsed = mySchema.parse({ ... })
parsed.foo()
Maybe docs aren't clear enough. If you find a better way to explain it there I'll be glad to accept a PR. :)