Validate schema and data - javascript

For an exercise I need to validate some data with a schema. I could validate the first level (id, name, value), checked if it's required or not, and that the type asked matched but I couldn't find a way for the nested one parents, do you know how it's possible?
function validate(data, schema){...}
const data = {
cards: [{
id: 1,
name: "Paris",
value: 99,
parents: [{
id: 8,
name: "Parent 1",
value: 200,
parents: []
}]
}]
}
const schema = [{
name: 'id',
required: true,
type: 'number'
}, {
name: 'name',
required: true,
type: 'string'
}, {
name: 'value',
required: true,
type: 'number'
}, {
name: 'parents',
required: false,
type: 'array:card'
}]
I'm stuck for the type array:card, where I'm supposed to check if what's within parents is similar to the root of the object. If possible without doing a for within a for because technically this object can be nested to infinity.
Thanks for your help

Recursion feels like it was born for this kind of problem. This demo uses slightly simplified validation criteria.
// Recursively applies a validation function to each card
const {cards} = getData();
for(const card of cards){
validate(card);
}
// Defines the validation function
function validate(node){
// Does something conditionally based on whether node passes tests
const valid =
typeof node.id == 'number' &&
typeof node.name == 'string' &&
typeof node.value == 'number' &&
Array.isArray(node.parents);
if(valid){ console.log(`node ${node.id} looks good`); }
else{ console.error(`node ${node.id} is invalid`); }
// If possible, moves down a level to validate more nodes
if(node.parents?.length){
for(const parent of node.parents){
validate(parent); // Recursive call
}
}
}
// Gets the initial data
function getData(){
return {
cards: [{
id: 1,
name: "Paris",
value: 99,
parents: [{
id: 8,
name: null,
value: 200,
parents: [{ id: 23, name: "Crockford", value: 86, parents: [] }]
}, {
id: 42,
name: "Nibbler",
value: 123,
parents: []
}]
}]
}
}

Related

Filter based on a String comparison Function

A am trying to filter the name property in each object (that is inside an array) based on whether the comparison function comes back true or not. However I am not sure if I am going about it correctly. I am using the localcomapre function. Below are my code snippets.
Below is my comparison hook.
export default function useLocalCompare(s1, s2) {
s1.trim();
s2.trim();
if (s1.localeCompare(s2, undefined, { sensitivity: "base" }) === 0) {
console.log("Both supplied parameters are the same");
return "True";
} else {
return "False";
}
}
And The filter function looks like
const filtteredarray = flatarrayofvalues.filter(
useLocalCompare(placeholder.name, genre)
);
Am I doing this directly. I only want it to filter out the object where the name property matches the genre value after both have gone through my useLocalCompare function.
Value of filtteredarray below
[{ id: 28, name: 'Action' },
{ id: 12, name: 'Adventure' },
{ id: 16, name: 'Animation' },
{ id: 35, name: 'Comedy' },
{ id: 80, name: 'Crime' },
{ id: 99, name: 'Documentary' },
{ id: 18, name: 'Drama' },
{ id: 10751, name: 'Family' },
{ id: 14, name: 'Fantasy' },
{ id: 36, name: 'History' },
{ id: 27, name: 'Horror' },
{ id: 10402, name: 'Music' },
{ id: 9648, name: 'Mystery' },
{ id: 10749, name: 'Romance' },
{ id: 878, name: 'Science Fiction' },
{ id: 10770, name: 'TV Movie' },
{ id: 53, name: 'Thriller' },
{ id: 10752, name: 'War' },
{ id: 37, name: 'Western' },
{ name: 'Trending' },
{ name: 'Top Rated' }
]
As already mentioned, you are returning strings with "True" and "False", which are not boolean values and so they do not evaluate properly in the filter().
Also, because your useLocalCompare function is just using an if statement evaluation, you can just return the if statement (as it evaluates to true or false itself).
So something like this should work:
export default function useLocalCompare(s1, s2) {
// This evaluates to true or false, so we can return it
return (s1.trim().localeCompare(s2.trim(), undefined, { sensitivity: "base" }) === 0);
}
It should also be noted that the filter method needs to use at least 1 parameter, being the current item from the array it is iterating through for evaluation. So your filter code needs to be slightly altered to use that parameter so it can actually loop through the flattarrayofvalues array and filter the results.
const filtteredarray = flatarrayofvalues.filter(placeholder =>
useLocalCompare(placeholder.name, genre)
);
However, because this is just an if statement evaluation, you could just incorporate the whole thing into the filter directly.
const filtteredarray = flatarrayofvalues.filter(placeholder =>
(placeholder.name.trim().localeCompare(genre.trim(), undefined, { sensitivity: "base" }) === 0)
);

How do I exclude null values when merging objects by key?

Lets say I have an array of objects in Javascript:
id: 1,
name: 'Snowy',
},
{
id: 2,
name: 'Quacky',
age: 13
},
{
id: 3,
name: 'Snowy',
age: 1,
},
{
name: null
}
]
I have created a function to arrange them by a key:
const filter =
(pets, key) =>
_(pets)
.filter(pets => _.has(pets, key))
.groupBy(key)
.value()
Although, the output of this function when called persists the null entry:
{
Snowy: [ { id: 1, name: 'Snowy' }, { id: 3, name: 'Snowy', age: 1 } ],
Quacky: [ { id: 2, name: 'Quacky', age: 13 } ],
null: [ { name: null } ]
}
Is there I way I can filter out any of the null values here?
You could try by changing the method you use in in your predicate function from _.has to _.get.
So, instead of checking if a path/ key exist in an object, you instead check the value of a path/ key within an object it its null or not.
note: you might also want to check for falsy values here (such as undefined) instead of just null, since _.get returns undefined if the path/ key does not exist
const pets = [{
id: 1,
name: 'Snowy',
},
{
id: 2,
name: 'Quacky',
age: 13
},
{
id: 3,
name: 'Snowy',
age: 1,
},
{
name: null
}
];
const filter =
(pets, key) =>
_(pets)
.filter(pets => _.get(pets, key) !== null)
.groupBy(key)
.value();
console.log(filter(pets, 'name'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
Why not first filter the array for objects that have a truthy name? For example someArray.filter(a => a.name)...group()...

Find Duplicate Object in List and Add Parameters

I am trying to find duplicate objects in a list of objects and add new parameters to the duplicate one.
Below snipped code is what I implemented so far. The problem is that it adds desired parameters to every object in the list.
const list = [{
id: 1,
name: 'test1'
},
{
id: 2,
name: 'test2'
},
{
id: 3,
name: 'test3'
},
{
id: 2,
name: 'test2'
}
];
const newList = list.reduce(
(unique, item) => (unique.includes(item) ? unique : [...unique, {
...item,
duplicated: true,
name: `${item.name}_${item.id}`
}]), []
);
console.log(newList);
Since there are two duplicate objects by id, the duplicated one should have duplicated and new name parameters. What part is wrong in my implementation?
By using findIndex method:
const list = [{
id: 1,
name: 'test1'
},
{
id: 2,
name: 'test2'
},
{
id: 3,
name: 'test3'
},
{
id: 2,
name: 'test2'
}
];
const newList = list.reduce(
(unique, item) => (unique.findIndex(x => x.id === item.id) > -1 ? [...unique, {
...item,
duplicated: true,
name: `${item.name}_${item.id}`
}] : [...unique, item]), []);
console.log(newList);
It can be written simply:
const
list = [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' },
{ id: 3, name: 'test3' },
{ id: 2, name: 'test2' }
],
uniqueList = list.reduce((arr, { id, name }) =>
arr.concat({
id,
name,
...arr.some(item => id === item.id) && { duplicate: true, name: `${name}_${id}` }
}), []);
console.log(uniqueList);
The problem was that when you called includes you were actually looking for an object whose pointer exists in the array.
In order to find an object which has property are the same as a requested property, you have no choice but to use functions such as some or every that is different from includes - you can send them a callback and not just an object.

Filter JavaScript array based on object property from another array

I have 2 arrays in JavaScript. One of which needs filtering based on a property from the other one.
I have a movies list such as this:
[
{
id: 1,
type: 'movies',
attributes: {
name: 'Batman'
}
},
{
id: 2,
type: 'movies',
attributes: {
name: 'Spiderman'
}
},
...
]
I then have another array which contains movies that a user has watched but they are within a nested object within the array as a relationship following the JSON API spec.
[
{
id: '1',
type: 'moviesWatched',
attributes: {
comment: 'Excellent film, would recommend to anyone who loves a action film!'
},
relationships: {
movie: {
data: {
type: 'movies',
id: 2
}
}
}
}
]
What I need to achieve is I need to cross reference the id from watchList[].relationships.movie.data.id with the first array. Obviously, this list is a lot longer in production but what I am hoping to achieve is to have a full list of movies which the use has watched with the name of the movie based off 2 arrays formatted like this to save me having to store needless data inside of the database.
The outcome of the array would be something along the lines of...
[
{
id: 1,
type: 'movies',
attributes: {
name: 'Batman'
},
meta: {
watched: false
}
},
{
id: 2,
type: 'movies',
attributes: {
name: 'Spiderman'
},
meta: {
watched: true
}
}
]
Here is what I have currently, which is working but I don't know if there is a better way to go about it...
movies.map((movie) => {
const watched = watchedMovies.find((searchable) => {
return searchable.relationships.movie.data.id === searchable.id;
});
movie.meta.watched = watched || false;
return movie;
});
Since you said the list can be long, you can extract the "watched" movie ids to a Set and then set the meta.watched property of a movie based on whether or not its id is in the set:
const movies = [{
id: 1,
type: 'movies',
attributes: {
name: 'Batman'
}
},
{
id: 2,
type: 'movies',
attributes: {
name: 'Spiderman'
}
}
];
const watched = [{
id: '1',
type: 'moviesWatched',
attributes: {
comment: 'Excellent film, would recommend to anyone who loves a action film!'
},
relationships: {
movie: {
data: {
type: 'movies',
id: 2
}
}
}
}];
const watchedSet = new Set(watched.map(m => m.relationships.movie.data.id));
movies.forEach(movie => movie.meta = {watched: watchedSet.has(movie.id)});
console.log(movies);

How to find the first property that is an array in an object?

I'm creating a function that loops through an array like this:
schema: [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}, {
And returns a callback with the values of the objects:
eachDeep (array, callback) {
array.forEach(item => {
item.fields.forEach(field => {
callback(field)
})
})
},
As you can see the item.fields.forEach part is harcoded. How can I modify the function so it detects the first property that it's an array and loop through it? (e.g. in this case that property is fields).
To find whether a property of an object is an array or not you can also use this one:
//let item be your object's property
if(typeof item == "object" && item.length > 0){
//do whatever if it is an array
}
You can check if the field is not an array or not, if so loop it, otherwise do something else with it.
var data = [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}, {
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}];
eachDeep (array, callback) {
array.forEach(item => {
// loop through each property again
item.forEach(prop => {
// if property is an array
if (prop instanceof Array) {
prop.forEach(field => callback(field));
} else {
// property is not an array
// do something else
}
})
})
},
var big_array =
[
{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}
];
for (let item of big_array)
{
for (let key in item)
{
if (Array.isArray(item[key]) )
{
console.log('this is an array do something:', key);
}
}
}
You could check using Array.isArray()
If the goal is to find the first array property you can do the following. Using ES6.
const schema = [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}]
let firstArr;
schema.forEach(item => {
firstArr = Object.keys(item).filter(k => Array.isArray(item[k]))[0];
})

Categories

Resources