How to change the value of matching property using json-query? - javascript

I using json-query because it lets me find by path inside the JSON object.
After I find it I want to replace the value with a new value and I expected it to affect the string, but it doesn't.
Is there a way to do it using with json-query? or rebuild this JSON again from the object but I can't find a way in the docs.
const jsonQuery = require("json-query");
var data = {
people: [
{ name: "Matt", country: "NZ" },
{ name: "Pete", country: "AU" },
{ name: "Mikey", country: "NZ" }
]
};
const b = jsonQuery("people[country=NZ].name", {
data: data
});
console.log({ b });
b.value = "bla";
console.log({ b }); // value === 'bla'
console.log({ data }); // value === 'Matt'
codesandbox.io

You can modify the entry in the reference subproperty returned by the query:
const b = jsonQuery("people[country=NZ].name", {
data: data
});
b.references[0].name = 'bla';
See proof-of-concept example: https://codesandbox.io/s/wonderful-kirch-g7zy2

Related

Best way to replace an array with its contents from inside an object

I'm given the following JavaScript object:
{
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
}
The name property (or other similar complex props) are always provided as an object within an array, even though there can only ever be a single value.
I need to save the information as an object that looks like this (without the array around the value of the name property):
{
name: {
firstName: 'First ',
lastName: 'Last'
},
age: 21
}
I need a generic function that doesn't reference a particular property name because that changes depending on the query. Here's my solution:
const object = {
name: [{"firstName":"First ","lastName":"Last"}],
age: 21
}
const data = {}
for (const property in object) {
const value = object[property]
if (Array.isArray(value)) {
data[property] = value[0]
} else {
data[property] = value
}
}
Which returns the properly formatted object.
My question is whether this is the most performant and/or most obvious way to get the result I'm looking for?
If you want abstraction over the entire object you could do something like this:
const object1 = {
name: [{"firstName":"First ","lastName":"Last"}],
age: 21
}
const rebuildObject = (object) => Object.keys(object).reduce((result, key) => {
const value = object[key];
result[key] = Array.isArray(value) ? object[key][0] : value;
return result;
}, {});
const newObject = rebuildObject(object1);
console.log(newObject);
If the name array is guaranteed to only ever have 1 object inside of it and is always an array, you can do:
const data = {
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
};
if(data.name.length === 0) {
const newObj = {
name: data.name[0],
age: data.age
};
};
console.log(newObj); // { firstName: 'First ', lastName: 'Last', age: 21 }
Edit
When name is actually any arbitray key name, you can do:
const data = {
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
};
const objKeys = Object.keys(data);
console.log(objKeys) // > Array ["name", "age"]
let arbKey = objKeys.filter(objKey => objKey !== "age")[0];
console.log(arbKey); // > "name"
const newObj = {
arbKey: data[arbKey][0],
age: data.age
};
console.log(newObj); // > Object { arbKey: Object { firstName: "First ", lastName: "Last" }, age: 21 };
Note: This only works based on the object schema you have provided. If your actual code is different, you will need to tweak it.
this is a generic function that can serve what you need, call the function with the object and property name you want to transform.
function arrayToObject(object, property) {
if(object[property] && Array.isArray(object[property])) {
object[property] = object[property][0];
}
return object;
}
// let data = {
// name: [
// {
// "firstName":"First ",
// "lastName":"Last"
// }
// ],
// age: 21
// }
// console.log(arrayToObject(data, 'name'));
// { name: { firstName: 'First ', lastName: 'Last' }, age: 21 }
update:
in case we don't know the property name,
we can use this version.
function arrayToObject(object) {
for(let key in object){
if(Array.isArray(object[key])) {
object[key] = object[key][0];
}
}
return object;
}
let a = {
name: [
{
"firstName":"First ",
"lastName":"Last"
}
],
age: 21
}
a.name = a.name[0];

React - Filter JSON array if key exists [duplicate]

I have an array of objects and I'm wondering the best way to search it. Given the below example how can I search for name = "Joe" and age < 30? Is there anything jQuery can help with or do I have to brute force this search myself?
var names = new Array();
var object = { name : "Joe", age:20, email: "joe#hotmail.com"};
names.push(object);
object = { name : "Mike", age:50, email: "mike#hotmail.com"};
names.push(object);
object = { name : "Joe", age:45, email: "mike#hotmail.com"};
names.push(object);
A modern solution with Array.prototype.filter():
const found_names = names.filter(v => v.name === "Joe" && v.age < 30);
Or if you still use jQuery, you may use jQuery.grep():
var found_names = $.grep(names, function(v) {
return v.name === "Joe" && v.age < 30;
});
You can do this very easily with the [].filter method:
var filterednames = names.filter(function(obj) {
return (obj.name === "Joe") && (obj.age < 30);
});
You can learn more about it on this MDN page.
You could utilize jQuery.filter() function to return elements from a subset of the matching elements.
var names = [
{ name : "Joe", age:20, email: "joe#hotmail.com"},
{ name : "Mike", age:50, email: "mike#hotmail.com"},
{ name : "Joe", age:45, email: "mike#hotmail.com"}
];
var filteredNames = $(names).filter(function( idx ) {
return names[idx].name === "Joe" && names[idx].age < 30;
});
$(filteredNames).each(function(){
$('#output').append(this.name);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"/>
var nameList = [
{name:'x', age:20, email:'x#email.com'},
{name:'y', age:60, email:'y#email.com'},
{name:'Joe', age:22, email:'joe#email.com'},
{name:'Abc', age:40, email:'abc#email.com'}
];
var filteredValue = nameList.filter(function (item) {
return item.name == "Joe" && item.age < 30;
});
//To See Output Result as Array
console.log(JSON.stringify(filteredValue));
You can simply use javascript :)
For those who want to filter from an array of objects using any key:
function filterItems(items, searchVal) {
return items.filter((item) => Object.values(item).includes(searchVal));
}
let data = [
{ "name": "apple", "type": "fruit", "id": 123234 },
{ "name": "cat", "type": "animal", "id": 98989 },
{ "name": "something", "type": "other", "id": 656565 }]
console.log("Filtered by name: ", filterItems(data, "apple"));
console.log("Filtered by type: ", filterItems(data, "animal"));
console.log("Filtered by id: ", filterItems(data, 656565));
filter from an array of the JSON objects:**
var names = [{
name: "Joe",
age: 20,
email: "joe#hotmail.com"
},
{
name: "Mike",
age: 50,
email: "mike#hotmail.com"
},
{
name: "Joe",
age: 45,
email: "mike#hotmail.com"
}
];
const res = _.filter(names, (name) => {
return name.name == "Joe" && name.age < 30;
});
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
So quick question. What if you have two arrays of objects and you would like to 'align' these object arrays so that you can make sure each array's objects are in the order as the other array's? What if you don't know what keys and values any of the objects inside of the arrays contains... Much less what order they're even in?
So you need a 'WildCard Expression' for your [].filter, [].map, etc. How do you get a wild card expression?
var jux = (function(){
'use strict';
function wildExp(obj){
var keysCrude = Object.keys(obj),
keysA = ('a["' + keysCrude.join('"], a["') + '"]').split(', '),
keysB = ('b["' + keysCrude.join('"], b["') + '"]').split(', '),
keys = [].concat(keysA, keysB)
.sort(function(a, b){ return a.substring(1, a.length) > b.substring(1, b.length); });
var exp = keys.join('').split(']b').join('] > b').split(']a').join('] || a');
return exp;
}
return {
sort: wildExp
};
})();
var sortKeys = {
k: 'v',
key: 'val',
n: 'p',
name: 'param'
};
var objArray = [
{
k: 'z',
key: 'g',
n: 'a',
name: 'b'
},
{
k: 'y',
key: 'h',
n: 'b',
name: 't'
},
{
k: 'x',
key: 'o',
n: 'a',
name: 'c'
}
];
var exp = jux.sort(sortKeys);
console.log('#juxSort Expression:', exp);
console.log('#juxSort:', objArray.sort(function(a, b){
return eval(exp);
}));
You can also use this function over an iteration for each object to create a better collective expression for all of the keys in each of your objects, and then filter your array that way.
This is a small snippet from the API Juxtapose which I have almost complete, which does this, object equality with exemptions, object unities, and array condensation. If these are things you need or want for your project please comment and I'll make the lib accessible sooner than later.
Hope this helps! Happy coding :)
The most straightforward and readable approach will be the usage of native javascript filter method.
Native javaScript filter takes a declarative approach in filtering array elements. Since it is a method defined on Array.prototype, it iterates on a provided array and invokes a callback on it. This callback, which acts as our filtering function, takes three parameters:
element — the current item in the array being iterated over
index — the index or location of the current element in the array that is being iterated over
array — the original array that the filter method was applied on
Let’s use this filter method in an example. Note that the filter can be applied on any sort of array. In this example, we are going to filter an array of objects based on an object property.
An example of filtering an array of objects based on object properties could look something like this:
// Please do not hate me for bashing on pizza and burgers.
// and FYI, I totally made up the healthMetric param :)
let foods = [
{ type: "pizza", healthMetric: 25 },
{ type: "burger", healthMetric: 10 },
{ type: "salad", healthMetric: 60 },
{ type: "apple", healthMetric: 82 }
];
let isHealthy = food => food.healthMetric >= 50;
const result = foods.filter(isHealthy);
console.log(result.map(food => food.type));
// Result: ['salad', 'apple']
To learn more about filtering arrays in functions and yo build your own filtering, check out this article:
https://medium.com/better-programming/build-your-own-filter-e88ba0dcbfae

How can I delete a key in a nested Javascript object with a dynamic number of properties and without eval?

I am looking to delete a specific key from a nested Javascript object based on a list of dynamic properties. Here is an example of what I mean:
This is a sample object:
employees: [
{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
I am looking to delete the address key when I am provided an array of ['location', 'building', 'address']
When I say "dynamic" I mean that I could also be provided with an array of ['employees', 1] so I cannot rely on a set number of nested properties.
The only approach that works for me right now is to use the dreaded eval, which is not a permanent solution since the Javascript objects that I am reading are written by users.
let jsObject = ... // the object shown above
let properties = ['location', 'building', 'address']
let evalString = ''
for (let i = 0; i < properties.length; i++){
evalString += '[\''+properties[i]+'\']'
}
eval('delete jsObject'+evalString)
What is an alternative to eval that will accomplish this same goal?
You could reduce the object by the keys and save the last key for deleting the object with that key.
function deleteKey(object, keys) {
var last = keys.pop();
delete keys.reduce((o, k) => o[k], object)[last];
return object;
}
var object = { employees: [{ name: "John", id: '1234567890', salary: 60000 }, { name: "Jack", id: '0987654321', salary: 55000 }], location: { building: { address: "111 Main St" } } };
console.log(deleteKey(object, ['location', 'building', 'address']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This method accepts an object and an array of properties, and removes the inner most property as required
function remove(obj, props) {
delete props.slice(0, -1).reduce((init, curr) => init && init[curr], obj)[[...props].pop()];
}
You can break your array into everything except the last element, get a reference to that and the call delete on the object using the last element. You can use reduce to easily build the object reference. You need to be careful with arrays because you can't use delete without leaving an empty slot — delete doesn't change the length.
Here's the basic idea:
function deleteProp(obj, keys){
let prop = keys.pop() // get last key
let c = keys.reduce((a, c) => a[c], obj) // get penultimate obj
if (Array.isArray(c)) c.splice(prop, 1) // if it's an array, slice
else delete c[prop] // otherwise delete
}
// Delete address
let obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['location', 'building', 'address'])
console.log(obj)
//Delete employee 1
obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['employees', 1])
console.log(obj)
//Delete employee 1 id
obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['employees', 1, 'id'])
console.log(obj)
Here is a sample that i'm sure could be trimmed down a bit but it explains each step and you should be able to see whats happening in it:
let jsObject = {
employees: [{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
};
let properties = ['location', 'building', 'address'];
// we use this to traverse the object storing the parent
let parent = null;
// run over each property in the array
for (let i = 0; i < properties.length; i++) {
// check if this is the last property and we have the parent object
if (i + 1 == properties.length && parent)
delete parent[properties[i]]; // just delete the property from the object
else if (parent === null)
parent = jsObject[properties[i]] // set the initial parent
else
parent = parent[properties[i]] // set the parent to the property in the existing object
}
// log the output
console.log(jsObject);
You are going to want to throw error handling and checks to make sure you don't end up outside the object as well.
Navigate to the object that contains the property you want to delete, then delete it:
let jsObject = {
employees: [
{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
};
let properties = ['location', 'building', 'address'];
let innerMost = jsObject;
for (let i = 0; i < properties.length - 1; i++) {
if (typeof innerMost !== "object" || innerMost === null) {
innerMost = null;
break;
}
innerMost = innerMost[properties[i]];
};
if (
innerMost !== null &&
typeof innerMost === "object" &&
properties.length > 0 &&
innerMost.hasOwnProperty(properties[properties.length - 1])
) {
delete innerMost[properties[properties.length - 1]];
console.log(jsObject);
} else console.log("Invalid path");

javascript filter by object key and return value of nested object key

I want to return the value of a key for one of the elements inside the object using a condition:
const raw = {
item1: { name: 'sdfd1', otherStuff: { book:'sdfd11' } },
item2: { name: 'sdfd2', otherStuff: { book:'sdfd22' } },
item3: { name: 'sdfd3', otherStuff: { book:'sdfd33' } }
};
var anotherOne = {
country1 : { city: 'one', item: 'item3'},
country2 : { city: 'two', item: 'item4'}
}
var searchTerm = anotherOne.country1.item; // item3
var secondTerm = someUser.otherInfo // 'otherStuff'
var result = Object.keys(raw)
.filter(key => {
if (key === searchTerm){
return raw[searchTerm][secondTerm].book
}})
console.log('result:' result); // sdfd33
Basically, i want to look for the searchTerm in the keys of the object raw, and return the value for the book key. In this example, it should return sdfd33.
My attempt is returning nothing.
updated:
updated the question.
Use square bracket [] while accessing a object key through a variable.
Hopefully the filter & Object.keys will not be required in this case
const raw = {
item1: {
name: 'sdfd1',
book: 'sdfd11'
},
item2: {
name: 'sdfd2',
book: 'sdfd22'
},
item3: {
name: 'sdfd3',
book: 'sdfd33'
}
};
var searchTerm = 'item3';
//using square bracket when acceing key using variable
var result = raw[searchTerm].book
console.log(result);
You can simply return the value like :
var result = raw.item3.book;
console.log(result);
result should be sdfd33

How to find duplicate values in a JavaScript array of objects, and output only unique values?

I'm learning JS. Supposing I have the below array of objects:
var family = [
{
name: "Mike",
age: 10
},
{
name: "Matt"
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
];
Notice that Nancy is showing up twice (changing only the age). Supposing I want to output only unique names. How do I output the above array of objects, without duplicates? ES6 answers more than welcome.
Related (couldn't find a good way for usage on objects):
Remove Duplicates from JavaScript Array
Easiest way to find duplicate values in a JavaScript array
EDIT Here's what I tried. It works well with strings but I can't figure how to make it work with objects:
family.reduce((a, b) => {
if (a.indexOf(b) < 0 ) {
a.push(b);
}
return a;
},[]);
You could use a Set in combination with Array#map and a spread operator ... in a single line.
Map returns an array with all names, which are going into the set initializer and then all values of the set are returned in an array.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = [...new Set(family.map(a => a.name))];
console.log(unique);
For filtering and return only unique names, you can use Array#filter with Set.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = family.filter((set => f => !set.has(f.name) && set.add(f.name))(new Set));
console.log(unique);
The Solution
Store occurrences of name external to the loop in an object, and filter if there's been a previous occurrence.
https://jsfiddle.net/nputptbb/2/
var occurrences = {}
var filteredFamily = family.filter(function(x) {
if (occurrences[x.name]) {
return false;
}
occurrences[x.name] = true;
return true;
})
you can also generalize this solution to a function
function filterByProperty(array, propertyName) {
var occurrences = {}
return array.filter(function(x) {
var property = x[propertyName]
if (occurrences[property]) {
return false;
}
occurrences[property]] = true;
return true;
})
}
and use it like
var filteredFamily = filterByProperty(family, 'name')
Explanation
Don't compare objects using indexOf, which only uses the === operator between objects. The reason why your current answer doesn't work is because === in JS does not compare the objects deeply, but instead compares the references. What I mean by that you can see in the following code:
var a = { x: 1 }
var b = { x: 1 }
console.log(a === b) // false
console.log(a === a) // true
Equality will tell you if you found the same exact object, but not if you found an object with the same contents.
In this case, you can compare your object on name since it should be a unique key. So obj.name === obj.name instead of obj === obj. Moreover another problem with your code that affects its runtime and not its function is that you use an indexOf inside of your reduce. indexOf is O(n), which makes the complexity of your algorithm O(n^2). Thus, it's better to use an object, which has O(1) lookup.
This will work fine.
const result = [1, 2, 2, 3, 3, 3, 3].reduce((x, y) => x.includes(y) ? x : [...x, y], []);
console.log(result);
With the code you mentioned, you can try:
family.filter((item, index, array) => {
return array.map((mapItem) => mapItem['name']).indexOf(item['name']) === index
})
Or you can have a generic function to make it work for other array of objects as well:
function printUniqueResults (arrayOfObj, key) {
return arrayOfObj.filter((item, index, array) => {
return array.map((mapItem) => mapItem[key]).indexOf(item[key]) === index
})
}
and then just use printUniqueResults(family, 'name')
(FIDDLE)
I just thought of 2 simple ways for Lodash users
Given this array:
let family = [
{
name: "Mike",
age: 10
},
{
name: "Matt",
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
]
1. Find duplicates:
let duplicatesArr = _.difference(family, _.uniqBy(family, 'name'), 'name')
// duplicatesArr:
// [{
// name: "Nancy",
// age: 2
// }]
2 Find if there are duplicates, for validation purpose:
let uniqArr = _.uniqBy(family, 'name')
if (uniqArr.length === family.length) {
// No duplicates
}
if (uniqArr.length !== family.length) {
// Has duplicates
}
Since most of the answers won't have a good performance, i thought i share my take on this:
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(i => {
uniqueObj[i.id] = i;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
We're leveraging the fact that keys are unique within objects. That means the last duplication item inside the first array, will win over its predecessors. If we'd want to change that, we could flip the array before iterating over it.
Also we're not bound to use only one property of our object for identifying duplications.
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(item => {
uniqueObj[`${item.id}_${item.name}`] = item;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
Or we could simply add a check, if the uniqueObj already holds a key and if yes, not overwrite it.
Overall this way is not very costly in terms of performance and served me well so far.
I would probably set up some kind of object. Since you've said ECMAScript 6, you have access to Set, but since you want to compare values on your objects, it will take a little more work than that.
An example might look something like this (removed namespace pattern for clarity):
var setOfValues = new Set();
var items = [];
function add(item, valueGetter) {
var value = valueGetter(item);
if (setOfValues.has(value))
return;
setOfValues.add(value);
items.push(item);
}
function addMany(items, valueGetter) {
items.forEach(item => add(item, valueGetter));
}
Use it like this:
var family = [
...
];
addMany(family, item => item.name);
// items will now contain the unique items
Explanation: you need to pull a value from each object as it's added and decide if it has already been added yet, based on the value you get. It requires a value getter, which is a function that given an item, returns a value (item => item.name). Then, you only add items whose values haven't already been seen.
A class implementation:
// Prevents duplicate objects from being added
class ObjectSet {
constructor(key) {
this.key = key;
this.items = [];
this.set = new Set();
}
add(item) {
if (this.set.has(item[this.key])) return;
this.set.add(item[this.key]);
this.items.push(item);
}
addMany(items) {
items.forEach(item => this.add(item));
}
}
var mySet = new ObjectSet('name');
mySet.addMany(family);
console.log(mySet.items);

Categories

Resources