I'm confused about Joi alternatives. I can't figure out how the alternatives are invoked against the item to tb validated.
function validateCourse(course) {
const objectSchema = {
id: Joi.number().required(),
name: Joi.string().min(3).required()
};
const arraySchema = Joi.array().items(Joi.object(objectSchema)).required();
return Joi.alternatives(objectSchema, arraySchema).validate(course);
}
This works for the object but not for the array of objects.
[
{
"id": 10,
"name": "XY"
},
{
"id": 11,
"name": "JFK"
}
]
I'm not sure if my array schema is at fault or my use of alternatives?
It appears it is working! However the invalidation message returned by the Joi when validating an array is vague when compared to validating the object.
validating object : "name" length must be at least 3 characters long
validating array : "value" must be an object
Which is not completely useful as now I have to potentially check hundreds of items to find the one at fault.
But that's a different issue consider this closed!
There is nothing wrong with your schema. The error you are getting with the array is:
"value" must be an object, "value" at position 0 fails because
[child "name" fails because ["name" length must be at least 3 characters long]]
Which is right, your name key must have at least 3 characters.
And, as you asked, you can get the object(s) that failed from the error description or
you can also use any.error(err) in order to keep track of those objects.
For instance, if you want to know the array indexes that failed you can do:
const arraySchema = Joi.array().items(Joi.object(objectSchema)).required().error((errors) => {
const indexes = errors.map((err) => err.path[0]);
return new Error(`Indexes with error: ${indexes}`)
})
For this array of objects:
[
{
'id': 11,
'name': 'ABC'
},
{
'id': 22,
'name': 'ABC'
},
{
'id': 33,
'name': 'XY'
},
{
'id': 44,
'name': 'ABC'
},
{
'id': 55,
'name': 'XY'
},
]
The error will be:
Indexes with error: 2,4
You can access your error message by doing:
validationResult.error.message
Don't forget to use { abortEarly: false } in your validation:
Joi.alternatives(objectSchema, arraySchema).validate(course, { abortEarly: false })
Worked as described for arrays but then displayed nothing for non arrays so I made a small change :
const arraySchema =
Joi.array().items(Joi.object(objectSchema)).required().error((errors) =>
{
if (errors[0].path.length)
{
const indexes = errors.map((err) => err.path[0]);
if (indexes.length) return new Error(`Indexes with error: ${indexes}`);
}
return errors;
});
So if it's an array I return the array of bad indexes (as you suggested). But if it's an object I just return { errors } unchanged.
Related
I need to create a Typescript Object in which a certain element with the key 'NONE' always remains at last. I need this to ensure that while rendering this object in my HTML page its value appears at bottom.
So basically I need an implementation of myObject.shiftKeyToLastPosition(keyname);
I've tried deleting and re-appending and reduce method. That didn't help.
If all your properties are string, you could try this
const source = {
'Domestic': '1',
'NONE': '0',
'Wild': '2',
};
const { NONE, ...dest } = source;
dest.NONE = source.NONE;
console.log(dest);
Here we are creating a new object without the NONE key and then adding NONE key at the last.
As per ECMAScript 2015 spec, the traversal of an object with string keys would yield keys in order of insertion.
P.S: Assuming that you have only integer and string keys, no Symbol keys.
As pointed out by proxima-b, there's no way to deterministically order an object.
What you can do though, is create an helper function that lets you define the order in which you'd like to display the key/values. The cool thing with Typescript is that you can do that in a type safe way!
const myObject = {
'hello3': 'Value 3',
'hello1': 'Value 1',
'hello2': 'Value 2',
'hello4': 'Value 4',
} as const;
function orderObjectToArrayKeyValue<Obj>(obj: Obj, orderKeys: { [key in keyof Obj]: number }): { key: keyof Obj, value: Obj[keyof Obj] }[] {
return Object
.entries<number>(orderKeys)
.sort(([, order1], [, order2]) => order1 < order2 ? -1 : 1)
.map(([key]) => ({ key, value: obj[key as keyof Obj] }) as { key: keyof Obj, value: Obj[keyof Obj] });
}
With the above example, if you call:
console.log(orderObjectToArrayKeyValue(myObject, {
hello1: 1,
hello2: 2,
hello3: 3,
hello4: 4,
}));
You'll get
[
{
"key": "hello1",
"value": "Value 1"
},
{
"key": "hello2",
"value": "Value 2"
},
{
"key": "hello3",
"value": "Value 3"
},
{
"key": "hello4",
"value": "Value 4"
}
]
Then using the framework of your choice, it'll be really easy to loop over that array and display the values (+ use the key if needed).
Here's a live example (press enter while focusing the left side and it'll run the code, the output will be displayed in your console).
I have a meta tag table which contains a tagId and a value.
I have an array of tagId,value objects
Eg : [{'tagId':2, 'value':33}, {'tagId':2, 'value':34}, {'tagId':1,
'value':34}, etc.. ]
My metaTag table consists of a virtual column which will return the {tagId,value} object for each entry in table. My question is how can I select rows with each {tagId, value} pair in the array.
In other words, I want to be able to do something like
[Sequelize.Op.in]:[{'tagId':2, 'value':33}, {'tagId':2, 'value':34}]
This doesn't work, however.
I might not have explained this well, English isn't my first language. Please ask if you need any clarification on the issue.
You can attain this by using Op.or. If I am not wrong you are trying
('tagId' = 2 and 'value' = 33) or ('tagId' = 2, 'value' = 34):
where: { [Sequelize.Op.or]: [{'tagId':2, 'value':33}, {'tagId':2, 'value':34}] }
You can add n number of values to the or array. As per your requirement.
if you want to do a in like this:
tagId in(2, 2) and value in (33, 34) then:
where: {'tagId':[2], 'value':[33, 34]}
You don't need the explicit Op.in for the array.
You can use there:
const myDeviceIds = [1, 2, 3, 4, 7, 9, 29];
const macroDevices = await MacroDevice.findAll({
where: {
deviceId: {
[Op.in]: myDeviceIds,
},
life: {
[Op.not]: null,
},
status: {
[Op.is]: null,
}
},
order: [
['id', 'DESC']
],
limit,
offset,
include: [
Macro,
Targets,
]
});
I am working with an API right now and I am using details[5].Value to target information in the following format:
details:
"value":[
{
"ID": "6",
"Name": "Links",
"Value": "URL"
},
{
"ID": "7",
"Name": "Other",
"Value": "URL"
}
etc
]
The problem is that the location inside of the JSON response is likely to change in the future, making my code obsolete and as the url has the potential to change as well, I cannot target that.
I want a way to target the value of url, mostly, because of this, by the value of the "Name" property. However, if I use something like
_.where(details, { Name: "Links" }).Value
It comes back as undefined. I am not sure if there would be another way to get to the information?
There are a couple points of confusion here.
_.where returns an array:
Looks through each value in the list, returning an array of all the values that contain all of the key-value pairs listed in properties.
so your _.where(details, obj).Value will (almost) always give you undefined because an array is unlikely to have a Value property. _.findWhere on the other hand does return a single value:
Looks through the list and returns the first value that matches all of the key-value pairs listed in properties.
Secondly, your details appears to look like:
let details = {
value: [
{ ID: '6', Name: 'Links', Value: 'URL' },
{ ID: '7', Name: 'Other', Value: 'URL' },
...
]
}
so you don't want to search details, you want to search details.value.
Putting them together:
_(details.value).findWhere({ Name: 'Links' }).Value
or
_.findWhere(details.value, { Name: 'Links' }).Value
You could use Array.prototype.find (or Array.prototype.filter if you're looking for all matches) and write your own callback but you already have Underscore available so why bother? Furthermore, Backbone collections have findWhere and where methods and there are advantages to matching Backbone's overall terminology.
Take a look at this mini function. Let me know if there is something wrong
Update
This is the ES5 Version
function f(key, value, array){
return array.value.filter(function(sub_array){
return sub_array[key] == value;
});
}
This is the ES6 Golfed Version
f=(k,v,a)=>a.value.filter(_=>_[k]==v)
//This is your JSON
var details = {
value: [
{
"ID": "6",
"Name": "Links",
"Value": "URL"
},
{
"ID": "7",
"Name": "Other",
"Value": "URL"
}
]
}
// Short code
f=(k,v,a)=>a.value.filter(_=>_[k]==v)
// f is the function name
// Recives k = array key, v = value, a = array
// filter by the given key and value
// return the result as an array
console.log(f('Name', 'Links', details))
An alternative is using the Javascript built-in function find to get a specific object within an array.
This alternative allows you to pass either an object or a string.
If the byThis parameter is an object, the whole set of key-values must match with the key-values of every object within the target array.
Otherwise, if byThis is a string every object will be treated as string to make the necessary comparison.
let details = { "value": [{ "ID": "6", "Name": "Links", "Value": "URL" }, { "ID": "7", "Name": "Other", "Value": "URL" }]};
let findBy = (array, byThis) => {
return array.find(o => {
if (typeof byThis === 'object') return Object.keys(byThis).every(k => o[k] === byThis[k]);
else if (typeof byThis === 'string') return o.toString() === byThis;
});
}
let found = findBy(details.value, {Name: "Links"});
console.log(found);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm currently working on a small application where I have to loop through an enormous array of objects. What would be the most efficient method to perform this?
var array = [
{
id: "1",
name: "Alpha"
},
{
id: "2",
name: "Beta"
},
...
];
I'd like to get each object where name equals "Alpha". I'm currently using a simple if statement to filter the objects with a different name value out, but I wonder if there's a more efficient way to do this, performance-wise.
It's worth to mention that I'll push the matching results into a new array.
No, there is no more efficient way.
The alternative is to build and maintain some kind of internal data structure which allows you to find the desired elements faster. As usual, the trade off is between the work involved in maintaining such a structure vs the time it saves you.
I don't have any way about which I would know it's more effective.
But if you had your objects ordered by name you could stop your search imideatly upon reaching an object whose name is not equal to "Alpha".
To find the first object you're looking for you can use binary search and from this Object you go up and down until at both ends you reach an object which isn't named "Alpha" or the end of array.
This is only a way of optimizing and will require time to sort the array and also will take more time when adding an element.
There's a JavaScript function exactly for this kind of task. Filter
From the Docs
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
Here is a small example by code for getting all element from array which has a certain 'name' field:
const arr = [
{name: 'Abc'},
{name: 'Xyz'},
{name: 'Lmn'},
{name: 'Xyz'},
{name: 'Xyz'}
];
let response = findByName('Xyz');
console.log(response);
function findByName (name) {
return arr.filter((element) => {
return element.name = name;
});
}
If you need more than one time a collection with a given name, you could use an object with the names as hashes and have instantly access to the items.
var array = [{ id: "1", name: "Alpha" }, { id: "2", name: "Beta" }, { id: "3", name: "Beta" }, { id: "4", name: "Gamma" }, { id: "5", name: "Beta" }, { id: "2", name: "Alpha" }],
hash = Object.create(null);
array.forEach(function (a) {
if (!hash[a.name]) {
hash[a.name] = [];
}
hash[a.name].push(a);
});
console.log(hash);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I want to create an object with an array property which looks like this:
var arrayOfUsers = {
id: "some user id",
username : "some names",
roles : [array with roles]
}
And i would like to access an element by id, something like, arrayOfUsers['some id']['roles'];
I am new to json. I've tried different ways, but always ended up with bunch of errors.
First, this is a JavaScript object. JSON is a string representation of JavaScript objects.
Second, it's important to know the difference between an object and an array. In general, consider Objects to be defined with curly braces { } and Arrays with braces [ ]
Values in Arrays are accessed by their index with the arr[index] syntax while objects use obj[key] syntax to access the value assigned to some key on the object.
For your scenario, I'd avoid using arrays, because you want to be able to access objects by key, not by index.
var users = {
"some user id": {
username : "some names",
roles : {
"some role id": {
name: "role name"
}
}
}
};
In reality, this isn't a very effective data structure, because you'd likely want to deal with arrays for looping, rendering, etc, but to answer your question about being able to index by the Id of user and role, this is how your data would have to be structured.
Here is how you declare:
var currentUser,
currentRole,
arrayOfUsers = {
id: 1,
username: "Sample Value",
roles: [{
roleId: 1,
name: "Admin"
},
{
roleId: 2,
name: "Cashier"
}]
};
This is how you access it:
for (var i = arrayOfUsers.length; i--;) {
currentUser = arrayOfUsers[i];
for (var x = currentUser.roles.length; x--;) {
currentRole = currentUser.roles[x];
console.log("ID=" + currentRole.id + "Name=" + currentRole.name);
}
}
First, you have to make difference between array which defined by [], and Objects, by {}.
If you want to make an array of JSON, you can do the following :
var arrayRoles = [{
idRole: 1,
type: 'admin'
}, {
idRole: 2,
type: 'user'
}];
var userArray = [{
id: 1,
username: 'toto',
roles: arrayRoles
}, {
id: 2,
username: 'titi',
roles: arrayRoles
}, {
id: 3,
username: 'toto',
roles: arrayRoles
}];
Then, if you want to iterate over all your data, you can do it by using forEach loop, which tends to be more elegant :
userArray.forEach(function(elm){
//Our roles array
var roles = elm.roles;
//For all item in roles array
roles.forEach(function(value){
//display type of role, for example.
console.log(value.type);
});
});
But if you want to search a specific item in your JSON array, you can use filter method, by using high order function.
function filterBy(key, filter){
return function(elm){
return elm[key] === filter;
}
}
Then, you can apply this function to your filter, by passing specific field and value, and it will return an array of results :
var filtered = userArray.filter(filterBy('username', 'toto'));
//Display '1'
console.log(filtered[0].id);
//Display '3'
console.log(filtered[1].id);