Imagine I have a data model like so:
// Pseudo-code, as I don't know how to achieve this
const Model = {
name: String.required,
description: String.optional,
rank: Number.required,
// ...
}
Basically, that means the Object must have a property name and it has to be a String. The property description is optional, but has to be a String if defined. Etc.
Now, let's say I have a bunch of Objects :
const obj1 = {
name: 'Object 1',
rank: 1,
}
const obj2 = {
description: 'This is object 2',
rank: 2,
}
In order to save my objects in a NoSQL database (Firebase's Firestore), I'd like to be sure that each Object matches the Model. This would prevent having missing fields for instance.
In my example, obj1 should pass but obj2 shouldn't has the propery name is missing.
And is there a way to throw in the error message which properties are missing?
Solution:
You can create a simple function that will validate your models based on an Array of required and optional parameters.
Note: The below has required and optional parameters hardcoded within the function, but you can also pass them in with very little effort and change in code. Simply remove the declarations and add them as parameters to the function.
Code
function validateModel(obj) {
let
// parameters specification
required = ["name", "rank"],
optional = ["description"],
// helper methods
toObjects = (a, v) => Reflect.has(obj, v) ? (a[v] = obj[v], a) : a,
grab = (type) => type.reduce(toObjects, {}),
payload = () => Object.assign(grab(required), grab(optional)),
notValid = v => !Reflect.has(obj, v);
//output
if (required.some(notValid)) return false;
return payload();
}
Working Example:
//setup
let log = (x) => { return console.log(x), log };
const Model={name:"n",description:"d",rank:"r"},Model2={rank:"a"},Model3={rank:"b",name:"b"};
// Validation Method:
function validateModel(obj) {
let
// parameters specification
required = ["name", "rank"],
optional = ["description"],
// helper methods
toObjects = (a, v) => Reflect.has(obj, v) ? (a[v] = obj[v], a) : a,
grab = (type) => type.reduce(toObjects, {}),
payload = () => Object.assign(grab(required), grab(optional)),
notValid = v => !Reflect.has(obj, v);
//output
if (required.some(notValid)) return false;
return payload();
}
// Examples:
log
(validateModel(Model) || "Invalid Model")
(validateModel(Model2) || "Invalid Model")
(validateModel(Model3) || "Invalid Model")
Making it Multi-Purpose:
If you create something like a model checker, it would be best to be able to check multiple types of Models with it. The below code creates a constructor that takes an Object full of Structs. Each Struct contains an array of required and optional parameters.
The ModelChecker constructor returns an object with a single method, validate. By passing in the object you want to check and the name of the model structure you want to check against, you can determine if a Model is valid.
Working Example:
//setup
let log = (x) => { return console.log(x), log };
const Model={name:"n",description:"d",rank:"r"},Model2={rank:"a"},Model3={rank:"b",name:"b"}, Car={"color":"black", "name": "toyota" }, Car2={"name":"chevy"};
//setup validation structures
const structs = {
"identity": {
required: ["name", "rank"],
optional: ["description"]
},
"car": {
required: ["color"],
optional: ["name"]
}
}
// model checker constructor
function ModelChecker(structs) {
let proto = {
structs
};
proto.validate = function validateModel(obj, struct) {
if (!this.structs[struct]) return false;
let
// parameters specification
required = this.structs[struct].required,
optional = this.structs[struct].optional,
// helper methods
toObjects = (a, v) => Reflect.has(obj, v) ? (a[v] = obj[v], a) : a,
grab = (type) => type.reduce(toObjects, {}),
payload = () => Object.assign(grab(required), grab(optional)),
notValid = v => !Reflect.has(obj, v);
//output
if (required.some(notValid)) return false;
return payload();
}
return proto;
}
// Examples:
let mc = ModelChecker(structs);
log
(mc.validate(Model, "identity") || "Invalid Model")
(mc.validate(Model2, "identity") || "Invalid Model")
(mc.validate(Model3, "identity") || "Invalid Model")
(mc.validate(Car, "car") || "Invalid Model")
(mc.validate(Car2, "car") || "Invalid Model")
Related
Let's say I have an object in which the value of each field is retrieved with an individual call:
let ExampleObj = {
foo: getFoo(),
bar: getBar(),
baz: getBaz(),
...
}
I have a function that will return the object if and only if all fields evaluate as non-null:
let returnsExampleObj = () => {
// if (!!ExampleObj.foo && !!ExampleObj.bar && !!ExampleObj.baz) {
// return ExampleObj
// } else {
// return undefined
// }
}
I can obviously just manually check each field to see whether it's null or not like in the above example, but is there a more elegant way of doing this? There may be dozens of fields in this object and to check each one manually would be very tedious.
Just check that .every of the values are truthy:
const returnsExampleObj = () => {
return Object.values(ExampleObj).every(Boolean) ? ExampleObj : undefined;
};
I would filter a list of objects using filter in JS
let dataid = infosStructured.filter(elm => {
console.log(elm.identifiant);
console.log("matching value", elm.identifiant == this.dataService.getcurrentId()); // I get a true value
elm.identifiant == this.dataService.getcurrentId();
});
console.log(dataid); // empty
My dataid is empty whereas elm.identifiant == this.dataService.getcurrentId() is true at a given moment
It has nothing to do with functional-programming. Try below code
let f = a => { a == 1 };
f(1); // undefined
let g = a => a == 1;
g(1); // true
let h => a => { return a == 1; };
h(1); // true
It's a good idea not to use inlined function to debug higher order function.
For multi line code inside Array.filter() you need to explicitly mention return. For single line like infosStructured.filter((elm) => elm) we do not specify the return keyword.
let dataid = infosStructured.filter(elm => {
console.log(elm.identifiant);
console.log("matching value", elm.identifiant == this.dataService.getcurrentId()); // I get a true value
elm.identifiant == this.dataService.getcurrentId();
return elm;
});
console.log(dataid);
If you do not need console.log() inside filter then you can change that in one liner without return keyword as:
let dataid = infosStructured.filter((elm) => elm.identifiant == this.dataService.getcurrentId());
console.log(dataid);
I've been using Folktale's Validation on a new project and I've found it really useful, but I have hit a wall with the need for sequential validations. I have a config object and I need to perform the following validations:
is is an Object?
are the object's keys valid (do they appear on a whitelist)?
are the values of the keys valid?
Each validation depends on the previous validation - if the item isn't an object, validating its keys is pointless (and will error), if the object has no keys, validating their values are pointless. Effectively I want to short-circuit validation if the validation fails.
My initial thought was to use Result instead of Validatio, but mixing the two types feels confusing, and I already havevalidateIsObject` defined and used elsewhere.
My current (working but ugly) solution is here:
import { validation } from 'folktale';
import { validateIsObject } from 'folktale-validations';
import validateConfigKeys from './validateConfigKeys';
import validateConfigValues from './validateConfigValues';
const { Success, Failure } = validation;
export default config => {
const wasObject = validateIsObject(config);
let errorMessages;
if (Success.hasInstance(wasObject)) {
const hadValidKeys = validateConfigKeys(config);
if (Success.hasInstance(hadValidKeys)) {
const hasValidValues = validateConfigValues(config);
if (Success.hasInstance(hasValidValues)) {
return Success(config);
}
errorMessages = hasValidValues.value;
} else {
errorMessages = hadValidKeys.value;
}
} else {
errorMessages = wasObject.value;
}
return Failure(errorMessages);
};
I initially took the approach of using nested matchWiths, but this was even harder to read.
How can I improve on this solution?
You can write a helper that applies validation rules until a Failure is returned. A quick example:
const validateUntilFailure = (rules) => (x) => rules.reduce(
(result, rule) => Success.hasInstance(result)
? result.concat(rule(x))
: result,
Success()
);
We use concat to combine two results. We use Success.hasInstance to check whether we need to apply the next rule. Your module will now be one line long:
export default config => validateUntilFailure([
validateIsObject, validateConfigKeys, validateConfigValues
]);
Note that this implementation doesn't return early once it sees a Failure. A recursive implementation might be the more functional approach, but won't appeal to everyone:
const validateUntilFailure = ([rule, ...rules], x, result = Success()) =>
Failure.hasInstance(result) || !rule
? result
: validateUntilFailure(rules, x, result.concat(rule(x)))
Check out the example below for running code. There's a section commented out that shows how to run all rules, even if there are Failures.
const { Success, Failure } = folktale.validation;
const validateIsObject = (x) =>
x !== null && x.constructor === Object
? Success(x)
: Failure(['Input is not an object']);
const validateHasRightKeys = (x) =>
["a", "b"].every(k => k in x)
? Success(x)
: Failure(['Item does not have a & b.']);
const validateHasRightValues = (x) =>
x.a < x.b
? Success(x)
: Failure(['b is larger or equal to a']);
// This doesn't work because it calls all validations on
// every item
/*
const validateItem = (x) =>
Success().concat(validateIsObject(x))
.concat(validateHasRightKeys(x))
.concat(validateHasRightValues(x))
.map(_ => x);
*/
// General validate until failure function:
const validateUntilFailure = (rules) => (x) => rules.reduce(
(result, rule) => Success.hasInstance(result)
? result.concat(rule(x))
: result,
Success()
);
// Let's try it out!
const testCases = [
null,
{ a: 1 },
{ b: 2 },
{ a: 1, b: 2 },
{ a: 2, b: 1 }
];
const fullValidation = validateUntilFailure([
validateIsObject,
validateHasRightKeys,
validateHasRightValues
]);
console.log(
testCases
.map(x => [x, fullValidation(x)])
.map(stringifyResult)
.join("\n")
);
function stringifyResult([input, output]) {
return `input: ${JSON.stringify(input)}, ${Success.hasInstance(output) ? "success:" : "error:"} ${JSON.stringify(output.value)}`;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>
I have a JSON object that looks a bit like this:
{
name: 'test',
details: {
description: 'This is the long description',
shortDescription: 'This is the short description (ironically longer than the description!)'
}
}
Obviously the real object is a lot more complicated than this example, but I have omitted the details because they will only complicate the question.
So, with this object, I have a function that tries to get the value of the property, it looks like this:
// Private function for matching fields
var _matchField = function (item, filter) {
// Our variables
var text = item[filter.field],
values = filter.expression.split(',');
// If we have any text
if (text) {
// Loop through our values
angular.forEach(values, function (value) {
console.log(text);
console.log(value);
// See if we have a match
if (text.toLowerCase().indexOf(value.toLowerCase()) > -1) {
// We have found a match
return true;
}
});
}
// We have found no matches
return false;
}
The issue is the line:
var text = item[filter.field],
If the property was just the name then item['name'] would work with the above object. But if I want to get the description; item['details.descrption'] doesn't work.
So I need a function that will allow me to specify a property name and it will find the property and return its value.
But before I try to write one, I was hoping there might be a simple solution that someone has come across.
you can write your custom function for this
function getProperty(json, field) {
if (json == null || field == null) {
return null;
}
var value = json;
var fields = field.split(".");
for (var i = 0; i < fields.length; i++) {
value = value[fields[i]];
if (value == null) {
return null;
}
}
return value;
}
check this plnkr example https://plnkr.co/edit/8Ayd9wnh1rJh1ycx5R1f?p=preview
You can split the reference to the object and use a function for getting the right nested object/value.
function getValue(o, p) {
if (typeof p === 'string') {
p = p.split('.')
}
return p.length ? getValue(o[p.shift()], p) : o;
}
var item = { name: 'test', details: { description: 'This is the long description', shortDescription: 'This is the short description (ironically longer than the description!)' } };
document.write(getValue(item, 'details.description'));
I solved this by creating this function:
// Private function to get the value of the property
var _getPropertyValue = function (object, notation) {
// Get all the properties
var properties = notation.split('.');
// If we only have one property
if (properties.length === 1) {
// Return our value
return object[properties];
}
// Loop through our properties
for (var property in object) {
// Make sure we are a property
if (object.hasOwnProperty(property)) {
// If we our property name is the same as our first property
if (property === properties[0]) {
// Remove the first item from our properties
properties.splice(0, 1);
// Create our new dot notation
var dotNotation = properties.join('.');
// Find the value of the new dot notation
return _getPropertyValue(object[property], dotNotation);
}
}
}
};
I know we can get all arguments in javascript inside function when it is called anywhere . we can get extra arguments which we didnt asked also .
But can we get only asked arguments on javascript function?
Like :
function a(a,b){
console.log(arguments);
}
if we call function a somewhere a(1,2,3,4,5)
then the output will be like [1,2,3,4,5]
but i want only [1,2] as i have expected only two params in function?
My condition is
index: (req, res, next) => {
var params = ['batch_id', 'section_id', 'subject_id', 'term', 'assesment_id', 'assesment_type'];
var _ = req._;
req.utils.requestValidation([req,res,next], params,'query')
// But i dont want to send params like above always instead like below
req.utils.requestValidation(arguments, params,'query')
and where it is called is
requestValidation: (data, options, param) => {
if (!options) return;
var _ = data[0]._ || data._;
var rules = {};
var data = {};
var sanity = {};
var elr = [validator.escape, validator.ltrim, validator.rtrim];
options.map((item, index) => {
rules[item] = 'required';
sanity[item] = elr;
});
data[param] = sanity;
if (typeof data != 'string') {
sanitizer.setOptions(data);
var data = sanitizer.sanitize(data[0], data[1], data[2],param);
return data[0].validation.validate(rules, data[0][param]);
}
return data.validation.validate(rules, data[param]);
},
if you need only two parameters just cut arguments to two items
if you want automatic this you can write function-wrapper, something like this:
function wrapperCutParams(func, paramsCount){
return function(){
var args = Array.prototype.slice(arguments, 0);
if(args.length > paramsCount){
args = args.slice(0, paramsCount)
}
func.apply(this, args)
}
}
Then
var a = function a(a,b){
console.log(arguments);
}
a = wrapperCutParams(a, 2)
Or just
a = wrapperCutParams(function a(a,b){
console.log(arguments);
}, 2)
Since you declared those arguments the most readable way would be to use them as they are, if you need to put them in an array, just do it.
myArgs = [a, b];
Write a higher-order function which takes the underlying function as a parameter, and returns a function which truncates the argument list to the number of arguments the function is asking for, based on its length property.
function slice_args(fn) {
return function() {
return fn.apply(this, Array.prototype.slice.call(arguments, 0, fn.length));
};
}
Then
function a(a,b){
console.log(arguments);
}
var b = slice_args(a);
b(1,2,3,4,5)
> [1, 2]