yup.js object validation, allow any key but values must be string or string array - javascript

I am using https://github.com/jquense/yup#yup
I want to have an object validation schema for:
subObjectField: {
[thisKeyCanBeAnyString]: string | string[] // allow string or array of strings
}
I cannot find an example or a starting point to achieve this, any ideas?

I've put together a function which makes this easy:
export const objectOf = (schema) => ({
name: 'objectOf',
exclusive: false,
message: "Object values don't match the given schema",
test: value => {
return value === null || Object.values(value).every(schema.isValidSync(value));
}
});
example:
yup.object().test(objectOf(yup.number())).nullable()
this successfully passes for null and for objects of numbers like { foo: 52, bar: -12 }

Related

Using lodash isEqual() to compare single object property with matching properties from large array with multiple objects

I've looked at lodash documentation and played around with comparing simple objects. I've also found a number of explanations online for comparing entire objects and other types of comparisons, but I want to compare one property value in a single object with the values of all properties of a certain name in a large array with multiple objects.
Is lodash smart enough to do this as is, and, if so, what would be the proper syntax to handle this? Or do I need some sort of loop to work through the larger object and recursively compare its properties of a certain name with the small object property?
The javascript comparison I'm looking for would be something like this, but I don't know how to indicate that I want to compare all itemURL properties in the large array:
// guard clause to end the larger function if test is true, any match found
if (_.isEqual(feedItem.link, rssDataFileArr.itemURL)) {
return;
}
Small object example:
const feedItem = {
link: 'https://news.google.com/rss/search?q=nodejs',
otherProperty: 'whatever'
}
Large array of objects example:
const rssDataFileArr = [
{
"itemURL": "https://news.google.com/rss/search?q=rss-parser",
"irrelevantProperty": "hello"
},
{
"itemURL": "https://news.google.com/rss/search?q=nodejs",
"irrelevantProperty": "world"
},
{
"itemURL": "https://news.google.com/rss/search?q=javascript",
"irrelevantProperty": "hello"
}
]
Any and all help appreciated.
As per suggestion in comment, I went with a built-in javascript method instead of lodash. I used some() because I only needed a true/false boolean result, not a find() value.
const feedItem = {
link: 'https://news.google.com/rss/search?q=nodejs',
otherProperty: 'whatever',
};
const rssDataFileArr = [
{
itemURL: 'https://news.google.com/rss/search?q=rss-parser',
irrelevantProperty: 'hello',
},
{
itemURL: 'https://news.google.com/rss/search?q=nodejs',
irrelevantProperty: 'world',
},
{
itemURL: 'https://news.google.com/rss/search?q=javascript',
irrelevantProperty: 'hello',
},
{
itemURL: 'https://news.google.com/rss/search?q=nodejs',
irrelevantProperty: 'world',
},
];
const linkMatch = rssDataFileArr.some(
({ itemURL }) => itemURL === feedItem.link
);
// guard clause to end the larger function if test is true, any match found
if (linkMatch) {
console.log('linkMatch is true');
return;
}

Mongoose automatically change the type of the value

In mongoose.model, I have chosen the type of name to be a string and the type of age to be a number, but when I enter a number as the value of name, I don't get an error and the same thing happens when I use something like '18' as the value of age.
Here is the code:
const User = mongoose.model('User', {
name: { type: String },
age: { type: Number }
});
const me = new User({
name: 12,
age: '18'
});
me.save().then(() => console.log(me)).catch(error => console.log(error));
Mongoose casts the values to the corresponding type, if it fails a CastError is thrown, from the doc:
Before running validators, Mongoose attempts to coerce values to the
correct type. This process is called casting the document. If casting
fails for a given path, the error.errors object will contain a CastError object.
You can try this by given age the value 'aa' for example.
If you want to override this behavior you can use one of the following options:
Disable casting globally: mongoose.Number.cast(false)
Disable casting just for a given path:
age: {
type: Number,
cast: false // Disable casting just for this path
},
Use a custom function:
age: {
type: Number,
cast: v => { return typeof v === 'number' && !isNaN(v) ? Number(v) : v; } // Override casting just for this path
}

Fastest way to filter object by multiple properties

I have an array of objects that I want to filter for a string. So I want to check multiple properties if they contain the filter string (case insensitive).
Here's the array:
[{
id: "01234",
name: "My Object 01234",
short_name: "MO01234"
}, ...]
So all of the following filter strings should match that object: 0123, obj, mO01 etc.
Here's what I have right now:
const filterString = this.filterString.toLowerCase();
return myObjects.filter(
entry => {
return
entry.id.toLowerCase().indexOf(filterString) >= 0 ||
entry.name.toLowerCase().indexOf(filterString) >= 0 ||
entry.short_name.toLowerCase().indexOf(filterString) >= 0;
}
);
Can you think of a faster/cleaner way to do that?
I don't think that you can do it faster, but cleaner may be something like that
const filterString = this.filterString.toLowerCase();
return myObjects.filter((entry) => {
return Object.values(entry).some((value) => {
return value.toLowerCase().includes(filterString)
})
});
If you are allowed to put additional properties in your object, perhaps you could concatenate id, name and short_name (already in lowercase) into a single string and store it in the object as e.g. search_key; then you'd only have to check that.
{
id: "01234",
name: "My Object 01234",
short_name: "MO01234",
search_key: "01234:my object 01234:mo01234"
}
return myObjects.filter(
entry => entry.search_key.indexOf(filterString) >= 0
);
One thing you have to be mindful of in this case is to prevent unintended matches that may arise because e.g. the last few characters of id and the first few characters of name together produce a match. This is why I used a : delimiter here, assuming that's a character that can't appear in an ID or a short name.
let objects = [{
id: "01234",
name: "My Object 01234",
short_name: "MO01234"
},
{
id: "test",
name: "test",
short_name: "test"
}];
const filter = (collection, searchFor) => {
return collection.filter(obj => Object.values(obj).reduce((a,b) => a || String(b).toLowerCase().indexOf(searchFor.toLowerCase()) > -1, false))
}
console.log(filter(objects, "0123"));
console.log(filter(objects, "obj"));
console.log(filter(objects, "mO01"));
You could also extend this function to take a set of columns as parameter to filter on.
Another version using Regex:
const filterRegex = (collection, searchFor) => {
return collection.filter(obj => Object.values(obj).reduce((a,b) => a || String(b).match(new RegExp(searchFor, 'gi')), false))
}
console.log(filterRegex(objects, "0123"));
console.log(filterRegex(objects, "obj"));
console.log(filterRegex(objects, "mO01"));

Flow: Inference error in for-of loop

I have a code this is trying to validate object with attributes id and _name for specified type. Attribute id should be a number and name should be a string as declared in FooT type.
function number(value: mixed): number {
if (typeof value === "number") return value
throw new TypeError("number required")
}
function string(value: mixed): string {
if (typeof value === "string") return value
throw new TypeError("string required")
}
function objectOf(attrs, value) {
const obj = {}
for (const key of Object.keys(attrs)) {
const typeFn = attrs[key]
obj[key] = typeFn(value[key])
}
return obj
}
type FooT = {
id: number,
name: string
}
const fooT: FooT = objectOf(
{
id: number,
name: string
},
{
id: 1,
name: "Foo"
}
)
Running flow shows this error. For some reason inferred return type of typeFn is not correctly determined in for-of loop when accessing object attribute values dynamically.
Cannot assign objectOf(...) to fooT because:
• string [1] is incompatible with number [2] in property id.
• number [3] is incompatible with string [4] in property name.
[3] 3│ function number(value: mixed): number {
:
[1] 8│ function string(value: mixed): string {
:
[2] 22│ id: number,
[4] 23│ name: string
24│ }
25│
26│ const fooT: FooT = objectOf(
27│ {
28│ id: number,
29│ name: string
30│ },
31│ {
32│ id: 1,
33│ name: "Foo"
34│ }
35│ )
36│
Is this an issue with flow or am I missing something?
It looks like you are running into issue #935 Type of object value not inferred correctly in for-in loop. You should be able to use the suppress_comment config and just put $FlowFixMe in the code to tell Flow to ignore that.

SimpleSchema match any type but null

I'm planning to make a collection to hold different app-wide settings, like, say, amount of logged in users today, Google analytics tracking ID, etc. So I made a schema like this:
options_schema = new SimpleSchema({
key: {
type: String,
unique: true
},
value: {
},
modified: {
type: Date
}
});
Now the main problem is that I want value to be of any type: Number, String, Date, or even custom Objects. Though it has to be present, can't be null.
But of course it gets angry about not specifying the type. Is there a workaround for this?
You can use Match patterns for your fields' type which allow you to do pretty much anything :
const notNullPattern = Match.Where(val => val !== null)
value : {
type : notNullPattern
}
(See Arrow functions)
Note that this will allow everything but null, including undefined.
Defining patterns this way allow you to use them everywhere in your application including in check :
check({
key : 'the key',
modified : Date.now(),
value : {} // or [], 42, false, 'hello ground', ...
}, optionsSchema)
Match.test(undefined, notNullPattern) //true
Match.test({}, notNullPattern) //true
Match.test(null, notNullPattern) //false
A more general solution to exclude one value would simply be:
const notValuePattern =
unwantedValue => Match.Where(val => val !== unwantedValue))
The use of which is similar to the above:
Match.test(42, notValuePattern(null)) // true
Note that due to the use of the identity operator === it will notably fail for NaN:
Match.test(NaN, notValuePattern(NaN)) // true :(
A solution could be:
const notValuePattern =
unwantedValue => Match.Where(val => Number.isNaN(unwantedValue)?
!Number.isNaN(val)
: val !== unwantedValue
)
Should you want a solution to exclude some specific values in a schema (kind of the contrary of Match.OneOf), you could use the following:
const notOneOfPattern = (...unwantedValues) =>
Match.Where(val => !unwantedValues.includes(val)
)
This uses Array.prototype.includes and the ... spread operator. Use as follow:
Match.test(42, notOneOfPattern('self-conscious whale', 43)) // true
Match.test('tuna', notOneOfPattern('tyranny', 'tuna')) // false
Match.test('evil', notOneOfPattern('Plop', 'kittens')) // true
const disallowedValues = ['coffee', 'unicorns', 'bug-free software']
Match.test('bad thing', notOneOfPattern(...disallowedValues)) // true

Categories

Resources