Hi there I am trying to find out if any key value is true of an object.
The following works only for objects without having nested objects.
I am trying to check if any key in the objects no matter parent or child has got true value
const odb = {
"all": true,
"allA": false,
"allB": false,
"allC": {
"allD": false,
"allE": false,
}
}
const isAnyKeyValueTrue = o => !Object.keys(o).find(k => !o[k]);
console.log(isAnyKeyValueTrue(odb));
Check if the value is an object, and if it is, call isAnyKeyValueTrue again. Also, to be more semantically correct, I have used some instead of find. I use && o[k] to make sure null is not given to the function (since typeof null === "object").
const odb = {
"all": false,
"allA": false,
"allB": false,
"allC": {
"allD": false,
"allE": { "allF": true },
}
}
const isAnyKeyValueTrue = o => Object.keys(o).some(k => typeof o[k] === "object" && o[k] ? isAnyKeyValueTrue(o[k]) : o[k] === true);
console.log(isAnyKeyValueTrue(odb));
The key point is that if we do not find matched result in current level,then we need to use || to find in the children level by invoke the current function recursively
const odb = {
"all": false,
"allA": false,
"allB": false,
"allC": {
"allD": false,
"allE": false,
'allF':{
'allG':true
}
}
}
const isAnyKeyValueTrue = o => Object.values(o).some(v => ((typeof v == `boolean`) && v) || isAnyKeyValueTrue(v))
console.log(isAnyKeyValueTrue(odb));
const odb = {
"all": false,
"allA": false,
"allB": false,
"allC": {
"allD": false,
"allE": { "allF": true },
}
}
const isTrue = (obj) => {
let result = false;
for (let key in obj) {
if (obj[key] === true) {
result = true;
break;
} else if (typeof obj[key] === 'object') {
result = isTrue(obj[key]);
}
}
return result;
}
console.log(isTrue(odb));
Related
If this is important, I am working in a react mob-x store. I have an object that I am converting to an array and looping through and doing various things. I need to find the last instance of the object where the value === true to then have the key to use in a comparison. (ex. if(panelName === panel (of the last instance where the value is true).
I am having trouble finding the last item where value === true. I tried using arr.length -1 but that of course just finds the last one regardless of what the value is. The object key and length are both variable, the value is either 'true or false'. Thank you.
panelsSaved = {EE: true, SS: false, RR: false, FF: true, WW: false}
#action expandNextPanel(panelName){
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
for (const [panel, value] of panels){
if(value === true && panel !== panelName){
//do stuff
break;
}
}
I am upvoting most of the answers because it sort of took a combination of a few of them to get this working.
let panelsUsed = {EE: true, SS: false, RR: false, FF: true, WW: false};
if(panelsUsed.length !== 0) {
for (let i = 0; i <= panelsUsed.length; i++) {
if(panelsUsed[i] !== panelName){
if(panelsUsed[i] !== undefined) {
'do stuff'
break;
} else {
'do other stuff'
}
}
};
}
let panelsFiltered = panelsUsed.filter((panel) => {return panel !== panelName});
this.filterValuesData.panelsUsed = panelsFiltered;
} ```
Thank you everyone for your input!
simply :
const panelsSaved = { EE: true, SS: false, RR: false, FF: true, WW: false }
const LastTrue = obj => Object.entries(obj)
.filter(([k,v])=>v)
.reverse()[0][0]
console.log( LastTrue(panelsSaved) )
OR, with a Loop
const panelsSaved = { EE: true, SS: false, RR: false, FF: true, WW: false }
function LastTrue(obj)
{
let rep;
for (let key in obj) if (obj[key]) rep=key
return rep
}
console.log( LastTrue(panelsSaved))
Just use classical for loop in reverse order (for...of loop cannot be used in such way).
panelsSaved = {EE: true, SS: false, RR: false, FF: true, WW: false}
#action expandNextPanel(panelName){
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
for (var i = panels.length - 1; i >= 0; i--) {
const [panel, value] = panels[i]
if(value === true && panel !== panelName){
// do stuff
break;
}
}
}
let panelsSaved = { EE: true, SS: false, RR: false, FF: true, WW: false };
function expandNextPanel(panelName) {
let lastTrue = Object.entries(panelsSaved).filter(
([panel, value]) => value === true && panel !== panelName
).pop();
console.log(lastTrue)
}
expandNextPanel("FF");
There's quite many ways to approach this, but here's a couple of ways.
You could iterate the array in reverse order and return the first one that matches true:
function lastWhere(arr, fn) {
if (arr.length === 0) return undefined;
for (let i = arr.length - 1; i >= 0; i--) {
const candidate = arr[i];
if (fn(candidate)) {
return candidate;
}
}
return undefined;
}
// ...
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
const [panel, value] = lastWhere(panels, ([panel, value]) => value === true)
Or perhaps you could use .map() to map the values to true or false and use .lastIndexOf() to find the last one that's true:
function lastWhereTrue(arr, fn) {
const mapped = arr.map((candidate) => fn(candidate));
const matchingIndex = arr.lastIndexOf(true);
return mapped[matchingIndex];
}
// ...
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
const [panel, value] = lastWhere(panels, ([panel, value]) => value === true)
Then, if you need to go through the entire list of panels and do something to all of them and specifically do something to the last panel, you could just compare either the panel or the value, depending on which one is unique.
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
const last = lastWhere(panels, ([panel, value]) => value === true)
for (const [panel, value] of panels) {
if (panel === last.panel) {
// ...
}
// or
if (value === last.value) {
// ...
}
}
Example of the solution which I suggested in the comments:
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
let lastTruePanel = null;
for (const [panel, value] of panels.reverse()) {
if (value === true && !lastTrueValueProcessed) {
// This is the entry which is the last value===true
lastTrueValueProcessed = [panel, value];
}
// ...
}
So what I'm trying to do is create a class method that compares two objects and returns custom data depending on the results. To get this custom data, I have destructured parameters that should let me customize what kind of data it returns. Here's the code so far for reference:
objects(obj1, obj2, { keysAndValues = true, returnSimplified = false, returnDetailed = false, keysOnly = false, valuesOnly = false }) {
try {
var results = [];
if (keysOnly == true) {
if (valuesOnly == true || keysAndValues == true) {
throw new SyntaxError('Two conflicting arguments cannot be chosen.');
}
Object.keys(obj1).forEach(o1 => Object.keys(obj2).forEach(o2 => {
if (o1 == o2) results.push(o1);
}));
} else if (valuesOnly == true) {
if (keysOnly == true || keysAndValues == true) {
throw new SyntaxError('Two conflicting arguments cannot be chosen.');
}
Object.values(obj1).forEach(o1 => Object.values(obj2).forEach(o2 => {
if (o1 == o2) results.push(o1);
}));
} else if (keysAndValues == true) {
if (valuesOnly == true || keysOnly == true) throw new SyntaxError('Two conflicting arguments cannot be chosen.');
results.push('Keys: ')
Object.keys(obj1).forEach(o1 => Object.keys(obj2).forEach(o2 => {
if (o1 == o2) results.push(o1);
}));
results.push('Values: ')
Object.values(obj1).forEach(o1 => Object.values(obj2).forEach(o2 => {
if (o1 == o2) results.push(o1);
}));
} else if (returnSimplified == true && returnDetailed == true) throw new SyntaxError('Two conflicting arguments cannot be chosen.');
var details = {
NumberOfMatchingResults: results.length,
ObjectName1: obj1,
ObjectName2: obj2,
ObjectKeys1: Object.keys(obj1),
ObjectKeys2: Object.keys(obj2),
ObjectValues1: Object.values(obj1),
ObjectValues2: Object.values(obj2)
};
return ((returnSimplified == true && !returnDetailed == true) ? results.length : (returnDetailed == true) ? details : results);
} catch (error) {
console.log(error.stack);
}
}
Now, it's not the prettiest piece of code out there, but for the most part, it gets the job done. The problem comes in when I try to call the method without one of the destructured parameters.
Example:
console.log(Compare.objects(obj1, obj2)); //returns 'Cannot read property "keysAndValues" of undefined'
I don't know what else to try, because nothing has worked so far, so I'm instead bringing the question here. Any help would be awesome.
The problem is that you need to set a default value for your destructured parameter itself. Just set its default value as an empty object to accomplish your desired outcome.
objects(obj1, obj2, {
keysAndValues = true,
returnSimplified = false,
returnDetailed = false,
keysOnly = false,
valuesOnly = false } = {}
) {
// method code here
}
You can clean up your method further by eliminating equality operators when checking for truthy or falsey values. if (valuesOnly) is preferred over if (valuesOnly == true).
Also, when comparing non-boolean values, you should always use strict equality operators to avoid bugs. So (o1 == o2) should be (o1 === o2)
Define your third parameter like this:
objects(obj1, obj2, booleans = {}) {
const {
keysAndValues = true, // default value
returnSimplified = false, // default value
returnDetailed = false, // default value
keysOnly = false, // default value
valuesOnly = false, // default value
} = booleans;
..... // block of code
}
at the time of calling the method do this:
// without third param
Compare.objects(obj1, obj2)
// with third param
Compare.objects(obj1, obj2, {
keysAndValues : true, // or false
returnSimplified : false, // or true
returnDetailed : false, // or true
keysOnly : false, // or true
valuesOnly : false, // or true
});
I have defined object with nested properties. I want to create a validator function which will check if another object has the same structure and value type as the one that I have defined!
The is the definition of the object:
const OBJECT_SCHEMA = {
name: String,
data: [{
isSelected: Boolean,
mId: String,
mSummary: String,
mMarkets: Array,
mBdd: String,
mReplaceDict: Object,
omId: String,
omnSummary: String,
omnMarkets: Array,
omnBdd: String,
omnReplaceDict: {
id: String,
text: String,
},
}],
metadata: {
emails: Array,
description: String,
},
};
And here is the function that I have for validation. Currently it works only with one nested level! I want it to validate with many nested levels.
function validateObjectStructure(schema, obj) {
let valid = true;
firstLevel: for(const k in schema) {
if(schema[k].constructor === Array) { // if prop is of type array
let i;
for(i = 0; i < schema[k].length; i++) {
for(const kk in schema[k][i]) {
if(!obj[k][i].hasOwnProperty(kk) || obj[k][i][kk].constructor !== schema[k][i][kk]) {
valid = false;
break firstLevel;
}
}
}
}
else if(schema[k].constructor === Object) { // if prop is of type object
for(const kk in schema[k]) {
if(!obj[k].hasOwnProperty(kk) || obj[k][kk].constructor !== schema[k][kk]) {
valid = false;
break firstLevel;
}
}
}
else { // if prop is simple type
if(!obj.hasOwnProperty(k) || obj[k].constructor !== schema[k]) {
valid = false;
break;
}
}
}
return valid;
}
Do you need to work with nested levels of the obj? If yes, you can do something like this instead of the last line:
Object.values(obj).reduce((accValid, value) => {
if (typeof value === 'object') {
return accValid && validateObjectStructure(schema, value);
}
return accValid;
}, valid);
return valid;
Here's a possible implementation:
function validate(obj, schema, path = '') {
let ok = true;
if (!obj)
ok = obj === schema;
else if (typeof schema === 'function')
ok = obj.constructor === schema;
else if (typeof obj !== 'object')
ok = obj === schema;
else if (Array.isArray(schema))
ok = Array.isArray(obj) && obj.every((x, k) => validate(x, schema[0], path + '[' + k + ']'));
else {
let ko = Object.keys(obj);
let ks = Object.keys(schema);
ok = ko.length === ks.length && ks.every(k => validate(obj[k], schema[k], path + '.' + k));
}
if (!ok)
throw new Error('FAILED ' + path);
return true;
}
// example:
const OBJECT_SCHEMA = {
name: String,
data: [{
isSelected: Boolean,
mId: String,
omnReplaceDict: {
id: String,
text: {
deepObj: {
deepProp: [Number]
}
},
},
}],
};
const obj = {
name: "foo",
data: [{
isSelected: true,
mId: "bar",
omnReplaceDict: {
id: "foo",
text: {
deepObj: {
deepProp: [1, 2, "???", 3]
}
},
},
}]
};
validate(obj, OBJECT_SCHEMA)
Note: although this home-made type checker appears to work correctly, it's quite limited (e.g. how to express "array of string-number pairs" or "either null or some object"?), so it might be an option to employ a real one, like Typescript. See here for a possible implementation.
I'm looking for something kind of like Object.keys but that works for potentially nested objects. It also shouldn't include keys that have object/array values (it should only include keys with immediate string/number/boolean values).
Example A
Input
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP"
}
Expected output
[
"check_id",
"check_name",
"check_type"
]
Object.keys would work for flat cases like this, but not for nested cases:
Example B
Input
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
}
}
}
Expected output
[
"check_id",
"check_name",
"check_type",
"check_params.basic_auth",
"check_params.encryption.enabled"
]
Note that this does not include tags, check_params, check_params.params, or check_params.encryption since these values are arrays/objects.
The question
Is there a library that does this? How would you implement it so that it can work with any object, large and nested, or small?
You could use reduce like this:
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return res;
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Edit1: For the general case where you want to include arrays.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"nested": [
{ "foo": 0 },
{ "bar": 1 }
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
A generator makes quick work of this kind of problem -
function* deepKeys (t, pre = [])
{ if (Array.isArray(t))
return
else if (Object(t) === t)
for (const [k, v] of Object.entries(t))
yield* deepKeys(v, [...pre, k])
else
yield pre.join(".")
}
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(Array.from(deepKeys(input)))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
Or a pure functional expression which eagerly computes all keys -
const deepKeys = (t, pre = []) =>
Array.isArray(t)
? []
: Object(t) === t
? Object
.entries(t)
.flatMap(([k, v]) => deepKeys(v, [...pre, k]))
: pre.join(".")
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(deepKeys(input))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
You could check the keys and iterate otherwise push the path to the result set.
function getKeys(object) {
function iter(o, p) {
if (Array.isArray(o)) { return; }
if (o && typeof o === 'object') {
var keys = Object.keys(o);
if (keys.length) {
keys.forEach(function (k) { iter(o[k], p.concat(k)); });
}
return;
}
result.push(p.join('.'));
}
var result = [];
iter(object, []);
return result;
}
var object = { check_id: 12345, check_name: "Name of HTTP check", check_type: "HTTP", tags: ["example_tag"], check_params: { basic_auth: false, params: ["size"], encryption: { enabled: true } } };
console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use for...in and create recursive function.
var obj = {"check_id":12345,"check_name":"Name of HTTP check","check_type":"HTTP","tags":["example_tag"],"check_params":{"basic_auth":false,"params":["size",{"a":"b"}],"encryption":{"enabled":true}}}
var keys = []
function getKeys(data, k = '') {
for (var i in data) {
var rest = k.length ? '.' + i : i
if (typeof data[i] == 'object') {
if (!Array.isArray(data[i])) {
getKeys(data[i], k + rest)
}
} else keys.push(k + rest)
}
}
getKeys(obj)
console.log(keys)
var json = {
id: '1234',
test: 'terst',
user : {
name: '',
details: {
address: {
add2: {
prim: "",
sec: ""
},
add1: '',
}
}
},
salary: {
cur: 1234,
net: 89797
},
age: 12
}
let arr = [];
let initialObj = {};
function getKeys(obj, parentK=''){
initialObj = arr.length === 0 ? obj: initialObj;
const entries = Object.entries(obj);
for(let i=0; i<entries.length; i++) {
const key = entries[i][0];
const val = entries[i][1];
const isRootElement = initialObj.hasOwnProperty(key);
parentK = isRootElement ? key: parentK+'.'+key;
arr.push(parentK)
if(typeof val === 'object' && val!==null && !Array.isArray(val)){
getKeys(val, parentK);
}
}
}
getKeys(json)
console.log('arr final---', arr);
Further enhanced above recommendation to return all keys including array.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return [...res,`${el}: ${obj[el].toString()}`];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res,...keyify(obj[el],`${prefix}${el}.`)];
}
return [...res,`${prefix}${el}: ${obj[el]}`];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Expected output:
[
'check_id: 12345',
'check_name: Name of HTTP check',
'check_type: HTTP',
'tags: example_tag',
'check_params.basic_auth: false',
'params: size',
'check_params.encryption.enabled: true',
'check_params.encryption.testNull: null'
]
Is this what you mean?
http://jsfiddle.net/robbiemilejczak/hfe12brb/1/
I couldn't do it with vanilla JS, and this is a pretty hacky solution that relies on lodash. Basically leverages lodashs _.forIn and _.isArray functions to iterate over an object. Also this will only go 1 layer deep, so objects inside of nested objects will be ignored. It does produce your expected output though, so I'd say it's a decent starting point.
The code below tells us if a value is found in a multi-level nested object.
I know that this can be easily achieved using recursion, I want to know how it can be done iteratively?
function deepSearch(o, value) {
Object.keys(o).forEach(function (k) {
if (o[k] !== null && typeof o[k] === 'object') {
return deepSearch(o[k],
}
if (o[k] === value)
return true;
});
}
A non-recursive version can look like this:
function deepSearch(obj, value) {
let queue = [obj],
found = false;
while (!found && queue.length) {
let o = queue.shift();
found = Object.keys(o).some(function (k) {
if (o[k] === value)
return true;
if (o[k] !== null && typeof o[k] === 'object')
queue.push(o[k]);
});
}
return found;
}
//
tree = {
a: {
b: 1,
c: {
d: 2
}
},
e: {
f: 3
}
};
console.log(deepSearch(tree, 3));
console.log(deepSearch(tree, 9));
The idea is, instead of process a child object immediately via a recursive call, we place it in a queue and keep looping until either the queue is empty or we find what we're looking for.
Also, your original version doesn't work as intended, you need .some there, not .forEach.
var data = {
prop1: 1,
prop2: {
prop21: true,
prop22: {
prop221: "prop221"
},
prop23: "prop23"
},
prop3: "prop3",
prop4: "prop4",
prop5: [{ prop511: true }, { prop512: false }, { prop513: true }, { prop514: true }],
prop6: [4, 5, 6],
prop7: [
[
{ prop7111: "prop7111" }
]
]
};
function TraverseObject(data) {
var currentContextObject = data;
var currentContextObjectProperties = Object.keys(data);
var contextStorage = []; //{contextObject: {}, contextObjectProperties: []}
while (currentContextObjectProperties.length !== 0 || contextStorage.length !== 0) {
var currentContextProperty = currentContextObjectProperties.shift();
if (currentContextProperty) {
if (!currentContextObject.hasOwnProperty(currentContextProperty)) {
break;
}
var currentContextPropertyValue = currentContextObject[currentContextProperty];
if (typeof currentContextPropertyValue === "object") {
contextStorage.push({
contextObject: currentContextObject,
contextObjectProperties: currentContextObjectProperties
});
currentContextObject = currentContextPropertyValue;
currentContextObjectProperties = Object.keys(currentContextObject);
continue;
}
console.log(`${currentContextProperty}--${currentContextPropertyValue}`);
}
if (currentContextObjectProperties.length === 0 && contextStorage.length !== 0) {
var popStorage = contextStorage.pop();
currentContextObject = popStorage.contextObject;
currentContextObjectProperties = popStorage.contextObjectProperties;
}
}
}
TraverseObject(data);
This will do the job.
Space complexity can be reduced a bit by deleting the properties already been visited.