Checking existence of nested property in an object javascript - javascript

I have already reviewed some of the answers to similar questions, however, I want to ask my question differently.
Let's say we have a string like "level1.level2.level3. ..." that indicates a nested property in an object called Obj.
The point is that we may not know how many nested properties exist in this string. For instance, it may be "level1.level2" or "level1.level2.level3.level4".
Now, I want to write a function, that given the Obj and the string of properties as input, to simply tell us if such a nested property exists in the object or not (let's say true or false as output).
Update:
Thanks to #Silvinus, I found the solution with a minor modification:
private checkNestedProperty(obj, props) {
var splitted = props.split('.');
var temp = obj;
for (var index in splitted) {
if (temp[splitted[index]] === 'undefined' || !temp[splitted[index]]) return false;
temp = temp[splitted[index]];
}
return true;
}

You could use Array#every() and thisArg of it, by iterating the keys and checking if it is in the given object.
var fn = function (o, props) {
return props.split('.').every(k => k in o && (o = o[k], true));
}
console.log(fn({}, "toto.tata")); // false
console.log(fn({ toto: { tata: 17 } }, "toto.tata")); // true
console.log(fn({ toto: { tata: { tutu: 17 } } }, "toto.foo.tata")); // false
console.log(fn({ toto: { tata: false } }, "toto.tata")); // true

You can explore your Obj with this function :
var fn = function(obj, props) {
var splited = props.split('.');
var temp = obj;
for(var index in splited) {
if(typeof temp[splited[index]] === 'undefined') return false;
temp = temp[splited[index]]
}
return true
}
var result = fn({ }, "toto.tata");
console.log(result); // false
var result = fn({ toto: { tata: 17 } }, "toto.tata");
console.log(result); // true
var result = fn({ toto: { tata: { tutu: 17 } } }, "toto.foo.tata");
console.log(result); // false
This function allow to explore nested property of Obj that depends of props passed in parameter

This answer provides the basic answer to your question. But it needs to be tweaked to handle the undefined case:
function isDefined(obj, path) {
function index(obj, i) {
return obj && typeof obj === 'object' ? obj[i] : undefined;
}
return path.split(".").reduce(index, obj) !== undefined;
}

Based on the solution given by #Silvinus here is a solution if you deal with array inside nested objects (as it is often the case in results from databases queries) :
checkNested = function(obj, props) {
var splited = props.split('.');
var temp = obj;
for(var index in splited) {
var regExp = /\[([^)]+)\]/;
var matches = regExp.exec(splited[index])
if(matches) {
splited[index] = splited[index].replace(matches[0], '');
}
if(matches) {
if(matches && typeof temp[splited[index]][matches[1]] === 'undefined') return false;
temp = temp[splited[index]][matches[1]];
}
else {
if(!matches && typeof temp[splited[index]] === 'undefined') return false;
temp = temp[splited[index]]
}
}
return true
}
obj = {ok: {ao: [{},{ok: { aa: ''}}]}}
console.log(checkNested(obj, 'ok.ao[1].ok.aa')) // ==> true
console.log(checkNested(obj, 'ok.ao[0].ok.aa')) // ==> false

Related

return key value pair from object whose key matches certain condition with javascript and underscore

var obj = {
"M_18-24":413109,
"F_18-24":366159,
"F_25-34":265007,
"U_25-34":1214,
"U_35-44":732
}
I want to return an object with key value pairs whose keys start with either "M" or "F". So the final object would look like
var obj = {
"M_18-24":413109,
"F_18-24":366159,
"F_25-34":265007
}
I've tried things like _.filter(obj, function(v,k) { return /^[MF]/.test(k) })...
this will do the trick:
function filte_obj_FM (inp)
{
var ret = {};
for ( var k in inp)
{
if ( k[0] == "M" || k[0] == "F" )
{
ret[k] = inp[k];
}
}
return ret;
}
see console output (F12 +-> see console) here: http://jsfiddle.net/vH3ym/2/
You can try
for (var prop in obj) { console.log(prop) }
It will give you the corresponding properties, then you can add your logic like
if(prop.indexOf('M'))
This should work:
var obj = {
"M_18-24":413109,
"F_18-24":366159,
"F_25-34":265007,
"U_25-34":1214,
"U_35-44":732
}
var filtered = {}
for(var key in obj) {
if(key[0] === "M" || key[0] === "F"){
filtered[key] = obj[key]
}
}
Another version this time using reduce:
// create a new underscore function that will return the properties from an object that pass a given predicate
_.mixin({ filterProperties: function(obj, predicate){
return _.reduce(obj, function(memo, value, key){
if( predicate(key) ){
memo[key] = value;
}
return memo;
}, {});
}});
// A couple of predicates that we can use when calling the new function
function isMale(key){
return key[0] == 'M';
}
function isFemale(key){
return key[0] == 'F';
}
// and finally getting the data we want:
var males = _.filterProperties( obj, isMale );
var females = _.filterProperties( obj, isFemale );
Here's my solution:
_.filter(_.keys(obj), function(key) {
return key.match(/U/);
}).forEach(function(kv) {
delete obj[kv];
});
I think all of yours are good and I voted them up. Thanks!

Detecting and fixing circular references in JavaScript

Given I have a circular reference in a large JavaScript object
And I try JSON.stringify(problematicObject)
And the browser throws
"TypeError: Converting circular structure to JSON"
(which is expected)
Then I want to find the cause of this circular reference, preferably using Chrome developer tools? Is this possible? How do you find and fix circular references in a large object?
Pulled from http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html. One line added to detect where the cycle is. Paste this into the Chrome dev tools:
function isCyclic (obj) {
var seenObjects = [];
function detect (obj) {
if (obj && typeof obj === 'object') {
if (seenObjects.indexOf(obj) !== -1) {
return true;
}
seenObjects.push(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
console.log(obj, 'cycle at ' + key);
return true;
}
}
}
return false;
}
return detect(obj);
}
Here's the test:
> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
Object {a: Object}
"cycle at a"
Object {b: Object}
"cycle at b"
true
#tmack's answer is definitely what I was looking for when I found this question!
Unfortunately it returns many false positives - it returns true if an object is replicated in the JSON, which isn't the same as circularity. Circularity means that an object is its own child, e.g.
obj.key1.key2.[...].keyX === obj
I modified the original answer, and this is working for me:
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (obj && typeof obj != 'object') { return; }
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (Object.prototype.hasOwnProperty.call(obj, k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
Here are a few very simple tests:
var root = {}
var leaf = {'isleaf':true};
var cycle2 = {l:leaf};
var cycle1 = {c2: cycle2, l:leaf};
cycle2.c1 = cycle1
root.leaf = leaf
isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
Here is MDN's approach to detecting and fixing circular references when using JSON.stringify() on circular objects: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value :
In a circular structure like the following
var circularReference = {otherData: 123};
circularReference.myself = circularReference;
JSON.stringify() will fail:
JSON.stringify(circularReference);
// TypeError: cyclic object value
To serialize circular references you can use a library that supports them (e.g. cycle.js) or implement a solution by yourself, which will require finding and replacing (or removing) the cyclic references by serializable values.
The snippet below illustrates how to find and filter (thus causing data loss) a cyclic reference by using the replacer parameter of JSON.stringify():
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
JSON.stringify(circularReference, getCircularReplacer());
// {"otherData":123}
You can also use JSON.stringify with try/catch
function hasCircularDependency(obj)
{
try
{
JSON.stringify(obj);
}
catch(e)
{
return e.includes("Converting circular structure to JSON");
}
return false;
}
Demo
function hasCircularDependency(obj) {
try {
JSON.stringify(obj);
} catch (e) {
return String(e).includes("Converting circular structure to JSON");
}
return false;
}
var a = {b:{c:{d:""}}};
console.log(hasCircularDependency(a));
a.b.c.d = a;
console.log(hasCircularDependency(a));
This is a fix for both #Trey Mack and #Freddie Nfbnm answers on the typeof obj != 'object' condition. Instead it should test if the obj value is not instance of object, so that it can also work when checking values with object familiarity (for example, functions and symbols (symbols aren't instance of object, but still addressed, btw.)).
I'm posting this as an answer since I can't comment in this StackExchange account yet.
PS.: feel free to request me to delete this answer.
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (!(obj instanceof Object)) { return; } // Now works with other
// kinds of object.
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
CircularReferenceDetector
Here is my CircularReferenceDetector class which outputs all the property stack information where the circularly referenced value is actually located at and also shows where the culprit references are.
This is especially useful for huge structures where it is not obvious by the key which value is the source of the harm.
It outputs the circularly referenced value stringified but all references to itself replaced by "[Circular object --- fix me]".
Usage:
CircularReferenceDetector.detectCircularReferences(value);
Note:
Remove the Logger.* statements if you do not want to use any logging or do not have a logger available.
Technical Explanation:
The recursive function goes through all properties of the object and tests if JSON.stringify succeeds on them or not.
If it does not succeed (circular reference), then it tests if it succeeds by replacing value itself with some constant string. This would mean that if it succeeds using this replacer, this value is the being circularly referenced value. If it is not, it recursively goes through all properties of that object.
Meanwhile it also tracks the property stack to give you information where the culprit value is located at.
Typescript
import {Logger} from "../Logger";
export class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue:boolean;
var circularExcludingStringifyResult:string = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any {
var serializedObjectCounter = 0;
return function (key: any, value: any) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
export class Util {
static joinStrings(arr: string[], separator: string = ":") {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
Compiled JavaScript from TypeScript
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue;
var circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n` +
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
static replaceRootStringifyReplacer(toBeStringifiedValue) {
var serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger_1.Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
};
}
}
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util {
static joinStrings(arr, separator = ":") {
if (arr.length === 0)
return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
exports.Util = Util;
Here is a Node ES6 version mixed from the answers from #Aaron V and #user4976005, it fixes the problem with the call to hasOwnProperty:
const isCyclic = (obj => {
const keys = []
const stack = []
const stackSet = new Set()
let detected = false
const detect = ((object, key) => {
if (!(object instanceof Object))
return
if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
const oldindex = stack.indexOf(object)
const l1 = `${keys.join('.')}.${key}`
const l2 = keys.slice(0, oldindex + 1).join('.')
console.log(`CIRCULAR: ${l1} = ${l2} = ${object}`)
console.log(object)
detected = true
return
}
keys.push(key)
stack.push(object)
stackSet.add(object)
Object.keys(object).forEach(k => { // dive on the object's children
if (k && Object.prototype.hasOwnProperty.call(object, k))
detect(object[k], k)
})
keys.pop()
stack.pop()
stackSet.delete(object)
})
detect(obj, 'obj')
return detected
})
There's a lot of answers here, but I thought I'd add my solution to the mix. It's similar to #Trey Mack's answer, but that solution takes O(n^2). This version uses WeakMap instead of an array, improving the time to O(n).
function isCyclic(object) {
const seenObjects = new WeakMap(); // use to keep track of which objects have been seen.
function detectCycle(obj) {
// If 'obj' is an actual object (i.e., has the form of '{}'), check
// if it's been seen already.
if (Object.prototype.toString.call(obj) == '[object Object]') {
if (seenObjects.has(obj)) {
return true;
}
// If 'obj' hasn't been seen, add it to 'seenObjects'.
// Since 'obj' is used as a key, the value of 'seenObjects[obj]'
// is irrelevent and can be set as literally anything you want. I
// just went with 'undefined'.
seenObjects.set(obj, undefined);
// Recurse through the object, looking for more circular references.
for (var key in obj) {
if (detectCycle(obj[key])) {
return true;
}
}
// If 'obj' is an array, check if any of it's elements are
// an object that has been seen already.
} else if (Array.isArray(obj)) {
for (var i in obj) {
if (detectCycle(obj[i])) {
return true;
}
}
}
return false;
}
return detectCycle(object);
}
And this is what it looks like in action.
> var foo = {grault: {}};
> detectCycle(foo);
false
> foo.grault = foo;
> detectCycle(foo);
true
> var bar = {};
> detectCycle(bar);
false
> bar.plugh = [];
> bar.plugh.push(bar);
> detectCycle(bar);
true
You can also use Symbols - thanks to that approach you won't have to mutate properties of the original object, apart from adding symbol for marking visited node.
It's cleaner and should be faster than gathering node properties and comparing with the object. It also has optional depth limitation if you don't want to serialize big nested values:
// Symbol used to mark already visited nodes - helps with circular dependencies
const visitedMark = Symbol('VISITED_MARK');
const MAX_CLEANUP_DEPTH = 10;
function removeCirculars(obj, depth = 0) {
if (!obj) {
return obj;
}
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = !obj || obj[visitedMark] || depth > MAX_CLEANUP_DEPTH;
// Copy object (we copy properties from it and mark visited nodes)
const originalObj = obj;
let result = {};
Object.keys(originalObj).forEach((entry) => {
const val = originalObj[entry];
if (!shouldSkip) {
if (typeof val === 'object') { // Value is an object - run object sanitizer
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
} else {
result[entry] = val;
}
} else {
result = 'CIRCULAR';
}
});
return result;
}
This will result in an object that has all the circular dependencies stripped and also does not go deeper than given MAX_CLEANUP_DEPTH.
Using symbols is safe as long as you don't do any meta-programming stuff on the object - they are transparent and they are not enumerable, hence - they will not show in any standard operations on the object.
Also, returning a new, cleaned up object has an advantage of not mutating the original one if you need to perform any additional operations on it.
If you don't want CIRCULAR marking, you can just modify the code a bit, hence skipping object before actually performing operations on it (inside the loop):
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const val = originalObj[entry];
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = val[visitedMark] || depth > MAX_SANITIZATION_DEPTH;
if (!shouldSkip) {
if (typeof val === 'object') { // Value is an object - run object sanitizer
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
} else {
result[entry] = val;
}
}
I just made this. It may be dirty, but works anyway... :P
function dump(orig){
var inspectedObjects = [];
console.log('== DUMP ==');
(function _dump(o,t){
console.log(t+' Type '+(typeof o));
for(var i in o){
if(o[i] === orig){
console.log(t+' '+i+': [recursive]');
continue;
}
var ind = 1+inspectedObjects.indexOf(o[i]);
if(ind>0) console.log(t+' '+i+': [already inspected ('+ind+')]');
else{
console.log(t+' '+i+': ('+inspectedObjects.push(o[i])+')');
_dump(o[i],t+'>>');
}
}
}(orig,'>'));
}
Then
var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b};
a.push(c); dump(c);
Says
== DUMP ==
> Type object
> x: (1)
>>> Type object
>>> 0: (2)
>>>>> Type number
>>> 1: (3)
>>>>> Type number
>>> 2: (4)
>>>>> Type number
>>> 3: [recursive]
> y: (5)
>>> Type object
>>> 0: [already inspected (1)]
>>> 1: (6)
>>>>> Type number
>>> 2: (7)
>>>>> Type number
>>> 3: (8)
>>>>> Type number
This tells that c.x[3] is equal to c, and c.x = c.y[0].
Or, a little edit to this function can tell you what you need...
function findRecursive(orig){
var inspectedObjects = [];
(function _find(o,s){
for(var i in o){
if(o[i] === orig){
console.log('Found: obj.'+s.join('.')+'.'+i);
return;
}
if(inspectedObjects.indexOf(o[i])>=0) continue;
else{
inspectedObjects.push(o[i]);
s.push(i); _find(o[i],s); s.pop(i);
}
}
}(orig,[]));
}
Here is #Thomas's answer adapted for node:
const {logger} = require("../logger")
// Or: const logger = {debug: (...args) => console.log.call(console.log, args) }
const joinStrings = (arr, separator) => {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
exports.CircularReferenceDetector = class CircularReferenceDetector {
detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
let value = toBeStringifiedValue[key];
let serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
logger.debug(`path "${joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
let isCircularValue;
let circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" is not the circular source`);
this.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
replaceRootStringifyReplacer(toBeStringifiedValue) {
let serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
I converted the answer of Freddie Nfbnm to TypeScript:
export class JsonUtil {
static isCyclic(json) {
const keys = [];
const stack = [];
const stackSet = new Set();
let detected = false;
function detect(obj, key) {
if (typeof obj !== 'object') {
return;
}
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
const oldIndex = stack.indexOf(obj);
const l1 = keys.join('.') + '.' + key;
const l2 = keys.slice(0, oldIndex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (const k in obj) { // dive on the object's children
if (obj.hasOwnProperty(k)) {
detect(obj[k], k);
}
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(json, 'obj');
return detected;
}
}
Just to throw my version into the mix... below is a remix of #dkurzaj 's code (which is itself a remix of #Aaron V 's, #user4976005 's, #Trey Mack 's and finally #Freddie Nfbnm 's [removed?] code) plus #darksinge 's WeakMap idea. So... this thread's Megamix, I guess :)
In my version, a report (rather than console.log'ed entries) is optionally returned as an array of objects. If a report is not required, testing stops on the first sighting of a circular reference (a'la #darksinge 's code).
Further, hasOwnProperty has been removed as Object.keys returns only hasOwnProperty properties (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys ).
function isCyclic(x, bReturnReport) {
var a_sKeys = [],
a_oStack = [],
wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
oReturnVal = {
found: false,
report: []
}
;
//# Setup the recursive logic to locate any circular references while kicking off the initial call
(function doIsCyclic(oTarget, sKey) {
var a_sTargetKeys, sCurrentKey, i;
//# If we've seen this oTarget before, flip our .found to true
if (wm_oSeenObjects.has(oTarget)) {
oReturnVal.found = true;
//# If we are to bReturnReport, add the entries into our .report
if (bReturnReport) {
oReturnVal.report.push({
instance: oTarget,
source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
duplicate: a_sKeys.join('.') + "." + sKey
});
}
}
//# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
else if (oTarget instanceof Object) {
a_sTargetKeys = Object.keys(oTarget);
wm_oSeenObjects.set(oTarget /*, undefined*/);
//# If we are to bReturnReport, .push the current level's/call's items onto our stacks
if (bReturnReport) {
if (sKey) { a_sKeys.push(sKey) };
a_oStack.push(oTarget);
}
//# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
//# NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
for (i = 0; i < a_sTargetKeys.length; i++) {
sCurrentKey = a_sTargetKeys[i];
//# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
if (oReturnVal.found && !bReturnReport) {
break;
}
//# Else if the sCurrentKey is an instanceof Object, recurse to test
else if (oTarget[sCurrentKey] instanceof Object) {
doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
}
}
//# .delete our oTarget into the wm_oSeenObjects
wm_oSeenObjects.delete(oTarget);
//# If we are to bReturnReport, .pop the current level's/call's items off our stacks
if (bReturnReport) {
if (sKey) { a_sKeys.pop() };
a_oStack.pop();
}
}
}(x, '')); //# doIsCyclic
return (bReturnReport ? oReturnVal.report : oReturnVal.found);
}
Most of the other answers only show how to detect that an object-tree has a circular-reference -- they don't tell you how to fix those circular references (ie. replacing the circular-reference values with, eg. undefined).
The below is the function I use to replace all circular-references with undefined:
export const specialTypeHandlers_default = [
// Set and Map are included by default, since JSON.stringify tries (and fails) to serialize them by default
{type: Set, keys: a=>a.keys(), get: (a, key)=>key, delete: (a, key)=>a.delete(key)},
{type: Map, keys: a=>a.keys(), get: (a, key)=>a.get(key), delete: (a, key)=>a.set(key, undefined)},
];
export function RemoveCircularLinks(node, specialTypeHandlers = specialTypeHandlers_default, nodeStack_set = new Set()) {
nodeStack_set.add(node);
const specialHandler = specialTypeHandlers.find(a=>node instanceof a.type);
for (const key of specialHandler ? specialHandler.keys(node) : Object.keys(node)) {
const value = specialHandler ? specialHandler.get(node, key) : node[key];
// if the value is already part of visited-stack, delete the value (and don't tunnel into it)
if (nodeStack_set.has(value)) {
if (specialHandler) specialHandler.delete(node, key);
else node[key] = undefined;
}
// else, tunnel into it, looking for circular-links at deeper levels
else if (typeof value == "object" && value != null) {
RemoveCircularLinks(value, specialTypeHandlers, nodeStack_set);
}
}
nodeStack_set.delete(node);
}
For use with JSON.stringify specifically, simply call the function above prior to the stringification (note that it does mutate the passed-in object):
const objTree = {normalProp: true};
objTree.selfReference = objTree;
RemoveCircularLinks(objTree); // without this line, the JSON.stringify call errors
console.log(JSON.stringify(objTree));
if you just need to see the content of that circular object, just use console.table(circularObj)
Try using console.log() on the chrome/firefox browser to identify where the issue encountered.
On Firefox using Firebug plugin, you can debug your javascript line by line.
Update:
Refer below example of circular reference issue and which has been handled:-
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = {};
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
alert("Circular reference found, discard key");
return;
}
alert("value = '" + value + "'");
// Store value in our collection
cache.push(value);
}
return value;
});
cache = null; // Enable garbage collection
var a = {b:1};
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o);
var obj = {
a: "foo",
b: obj
};
var replacement = {"b":undefined};
alert("Result : " + JSON.stringify(obj,replacement));
Refer example LIVE DEMO

Access JavaScript property case-insensitively?

Assume I have an object:
var obj = {
foo:"bar",
fizz:"buzz"
};
I need to access a property of that object dynamically like so:
var objSetter = function(prop,val){
obj[prop] = val;
}
No problems there, except for that prop needs to be case insensitive in case the property name is passed into the function as, say, Foo instead of foo.
So how can I point to an object's property by name without regard to case? I would like to avoid iterating the entire object if possible.
Try this:
var myObject = { "mIxeDCaSEKeY": "value" };
var searchKey = 'mixedCaseKey';
var asLowercase = searchKey.toLowerCase();
myObject[Object.keys(myObject).find(key => key.toLowerCase() === asLowercase)];
You can alternatively already provide the searchKey in lowercase.
If you want it as a function:
/**
* #param {Object} object
* #param {string} key
* #return {any} value
*/
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowerCase();
return object[Object.keys(object)
.find(k => k.toLowerCase() === asLowercase)
];
}
If the key can't be found, then it'll return undefined, just like normal.
If you need to support older browsers, then you can use filter instead:
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowercase();
return object[Object.keys(object).filter(function(k) {
return k.toLowerCase() === asLowercase;
})[0]];
}
I suggest using the polyfills for Object.keys() and Array.filter() if you need even older support.
Note: If you want to also check non-enumerable keys, use Object.getOwnPropertyNames() instead of Object.keys().
Nerdy Note: This assumes your Object doesn't have a key undefined (eg: const foo = {[undefined]: 'bar'};). That's just weird.
Compare all the properties of obj with prop.
var objSetter = function(prop,val){
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
obj[p] = val;
break;
}
}
}
For this, I prefer using the prototype over a standalone function just for ease of use and expressiveness. I just don't like funneling objects into functions if I don't have to.
Also, while the accepted answer works, I wanted a more comprehensive solution for both getting and setting that would behave as much like the native dot notation or bracket notation as possible.
With that in mind, I created a couple prototype functions for setting/getting an object property without regard to case. You have to remember to be VERY responsible when adding to the Object prototype. Especially when using JQuery and other libraries. Object.defineProperty() with enumerable set to false was used specifically to avoid conflict with JQuery. I also didn't bother naming the functions anything that indicates they are case-insensitive, but you certainly could. I like shorter names.
Here's the getter:
Object.defineProperty(Object.prototype, "getProp", {
value: function (prop) {
var key,self = this;
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
return self[key];
}
}
},
//this keeps jquery happy
enumerable: false
});
Here's the setter:
Object.defineProperty(Object.prototype, "setProp", {
value: function (prop, val) {
var key,self = this;
var found = false;
if (Object.keys(self).length > 0) {
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
//set existing property
found = true;
self[key] = val;
break;
}
}
}
if (!found) {
//if the property was not found, create it
self[prop] = val;
}
return val;
},
//this keeps jquery happy
enumerable: false
});
Now that we've created those functions, our code is super clean and concise and just works.
Case-insensitive getting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.getProp("FOO"); //returns 'bar'
obj.getProp("fOO"); //returns 'bar'
obj.getProp("CAMELCASE"); //returns 'humpy'
obj.getProp("CamelCase"); //returns 'humpy'
Case-insensitive setting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.setProp('CAmelCasE', 'super humpy'); //sets prop 'camelCase' to 'super humpy'
obj.setProp('newProp', 'newval'); //creates prop 'newProp' and sets val to 'newval'
obj.setProp('NewProp', 'anotherval'); //sets prop 'newProp' to 'anotherval'
Yet another variation on those already presented which pushes the iteration down into the Underscore/Lodash findKey function:
var _ = require('underscore');
var getProp = function (obj, name) {
var realName = _.findKey(obj, function (value, key) {
return key.toLowerCase() === name.toLowerCase();
});
return obj[realName];
};
For example:
var obj = { aa: 1, bB: 2, Cc: 3, DD: 4 };
getProp(obj, 'aa'); // 1
getProp(obj, 'AA'); // 1
getProp(obj, 'bb'); // 2
getProp(obj, 'BB'); // 2
getProp(obj, 'cc'); // 3
getProp(obj, 'CC'); // 3
getProp(obj, 'dd'); // 4
getProp(obj, 'DD'); // 4
getProp(obj, 'EE'); // undefined
This answer requires ES6.
const x = { 'aB': 1, 'X-Total-Count': 10, y3: 2 }
console.log(x[Object.keys(x).find(key=>{return key.match(/^ab$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^x-total-count$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^y3$/i)})])
It seems to me like a good candidate for Proxy with traps to convert string keys to either upper case or lower case and behaving like a regular object.
This works with either notation: dots or braquets
Here is the code:
'use strict';
function noCasePropObj(obj)
{
var handler =
{
get: function(target, key)
{
//console.log("key: " + key.toString());
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
return target[key];
return target[uKey];
}
return target[key];
},
set: function(target, key, value)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
target[key] = value;
target[uKey] = value;
}
else
target[key] = value;
},
deleteProperty: function(target, key)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
delete target[key];
if (uKey in target)
delete target[uKey];
}
else
delete target[key];
},
};
function checkAtomic(value)
{
if (typeof value == "object")
return new noCasePropObj(value); // recursive call only for Objects
return value;
}
var newObj;
if (typeof obj == "object")
{
newObj = new Proxy({}, handler);
// traverse the Original object converting string keys to upper case
for (var key in obj)
{
if (typeof key == "string")
{
var objKey = key.toUpperCase();
if (!(key in newObj))
newObj[objKey] = checkAtomic(obj[key]);
}
}
}
else if (Array.isArray(obj))
{
// in an array of objects convert to upper case string keys within each row
newObj = new Array();
for (var i = 0; i < obj.length; i++)
newObj[i] = checkAtomic(obj[i]);
}
return newObj; // object with upper cased keys
}
// Use Sample:
var b = {Name: "Enrique", last: "Alamo", AdDrEsS: {Street: "1233 Main Street", CITY: "Somewhere", zip: 33333}};
console.log("Original: " + JSON.stringify(b)); // Original: {"Name":"Enrique","last":"Alamo","AdDrEsS":{"Street":"1233 Main Street","CITY":"Somewhere","zip":33333}}
var t = noCasePropObj(b);
console.log(JSON.stringify(t)); // {"NAME":"Enrique","LAST":"Alamo","ADDRESS":{"STREET":"1233 Main Street","CITY":"Somewhere","ZIP":33333}}
console.log('.NaMe:' + t.NaMe); // .NaMe:Enrique
console.log('["naME"]:' + t["naME"]); // ["naME"]:Enrique
console.log('.ADDreSS["CitY"]:' + t.ADDreSS["CitY"]); // .ADDreSS["CitY"]:Somewhere
console.log('check:' + JSON.stringify(Object.getOwnPropertyNames(t))); // check:["NAME","LAST","ADDRESS"]
console.log('check2:' + JSON.stringify(Object.getOwnPropertyNames(t['AddresS']))); // check2:["STREET","CITY","ZIP"]
You could do this in order to "normalize" prop
var normalizedProp = prop.toLowerCase();
obj[normalizedProp] = val;
const getPropertyNoCase = (obj, prop) => obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
or
const getPropertyNoCase = (obj, prop) => {
const lowerProp = prop.toLowerCase(obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
}
The ES6 example posted by #nilloc is incorrect and will break in use.
Here is a working example:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i)
} else {
return result;
}
},null)]);
or better yet, it should return undefined if the key doesn't exist:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i) || undefined
} else {
return result;
}
},undefined)]);
One consideration is that the above example will return the last matching key in the object if there are multiple keys that match.
Here is an example with the code made into a function:
/**
* #param {Object} object
* #param {string} key
* #return {string||undefined} value || undefined
*/
function getKeyCase(obj,key) {
const re = new RegExp(key,"i");
return Object.keys(obj).reduce((result,key)=>{
if (!result) {
return key.match(re) || undefined
} else {
return result;
}
},undefined);
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[getKeyCase(x,"x-total-count")]);
Its really sad that the iteration can't be skipped as it seems. For me what is acceptable but may not be for everyone is to shape the object one time via iteration and then use it in regular hashmap fashion.
const hashmap = {
'FOO': 'foo as in function programming',
'bar': 'bar is in baz',
};
const shapedmap = Object.entries(hashmap).reduce(
(acc, [key, val]) => (acc[key.toUpperCase()] = val, acc), {}
);
for (const term of ['foo', 'bar', 'baz']) {
const match = shapedmap[term.toUpperCase()]
match && console.log('awesome, we got the term.', match);
};
Even if it just one time lookup has to be performed, it shouldn't less performant as any other iteration solution since after 1 pass, the lookup speed is constant. (I guess).
This is an old question, but it was the first one I found.
As #ZachSmith says, you can use a Proxy.
Here's some example code:
function lowercase(oldKey) {
// Check that it's a string.
return typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey;
}
const propertiesMap = new Map(
Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]])
);
const caseInsensitiveGetHandler = {
get: function(target, property, receiver) {
return propertiesMap.get(lowercase(property));
}
};
obj = new Proxy(obj, caseInsensitiveGetHandler);
For my use case, I only needed to proxy the object's getter, but you may need to implement more of the Proxy methods.
There is no need for any iteration. Since prop might not be a string, it should be coerced to a string first where appropriate since that's what objects do natively. A simple getter function is:
function objGetter(prop) {
return obj[String(prop).toLowerCase()];
}
If there is a requirement is to restring access to own properties:
function objGetter(prop) {
prop = String(prop).toLowerCase();
if (obj.hasOwnProperty(prop)) {
return obj.prop;
}
}
and a setter:
function objSetter(prop, val) {
obj[String(prop).toLowerCase()] = val;
}
Heres a very simple code to do this
Assuming that data is the array of objects like
data=[{"A":"bc","B":"nn"}]
var data=data.reduce(function(prev, curr) {
var cc = curr; // current value
var K = Object.keys(cc); // get all keys
var n = {};
for (var i = 0; i < K.length; i++) {
var key = K[i];//get hte key
n[key.toLowerCase()] = cc[key] // convert to lowercase and assign
}
prev.push(n) // push to array
return prev;
}, [])
Output will be
data=[{"a":"bc","b":"nn"}]
You might only need to do case-insensitive matching (usually expensive because of object iteration) IF a case-sensitive match (cheap and quick) fails.
Say you have:
var your_object = { "Chicago" : 'hi' , "deTroiT" : 'word' , "atlanta" : 'get r dun' } ;
And you have, for whatever reason, the_value, Detroit:
if( your_object.hasOwnProperty( the_value ) )
{
// do what you need to do here
}
else
{ // since the case-sensitive match did not succeed,
// ... Now try a the more-expensive case-insensitive matching
for( let lvs_prop in your_object )
{ if( the_value.toLowerCase() == lvs_prop.toLowerCase() )
{
// do what you need to do here
break ;
} ;
}
} ;
why would we do it that complicated when we simply can make it all lower case:
var your_object = {
"chickago" : 'hi' ,
"detroit" : 'word',
"atlanta" : 'get r dun',
GetName: function (status) {
return this[status].name;
} };
to call it: your_object.GetName(your_var.toLowerCase());
Another simple way:
function getVal(obj, prop){
var val;
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
val = obj[p]
break;
}
}
return val;
}
Use it like this:
var obj = {
foo:"bar",
fizz:"buzz"
};
getVal(obj,"FoO") -> returns "bar"
Here is a nice recursive function that allows you to traverse a javascript object in a case-insensitive way:
let testObject = {'a': {'B': {'cC': [1,2,3]}}}
let testSeq = ['a','b','cc']
function keySequence(o, kseq) {
if(kseq.length==0){ return o; }
let validKeys = Object.keys(o).filter(k=>k.toLowerCase()==kseq[0].toLowerCase());
if(validKeys.length==0) { return `Incorrect Key: ${kseq[0]}` }
return keySequence(o[validKeys[0]], kseq.slice(1))
}
keySequence(testObject, testSeq); //returns [1,2,3]
This will convert everything to lowercase, but in a bind this could help if you are not concerned with retaining case.
var somedata = {
"MixEdCase": 1234
}
var temp = JSON.parse(JSON.stringify(somedata).toLowerCase());
console.log(temp.mixedcase);
// or
console.log(temp["mixedcase"]);
So, you will need to get the object key that matches the case of the existing object, then use this to do your object update.
const obj = {
foo:"bar",
fizz:"buzz"
};
// to get obj.foo or obj.FOO or obj.foO returning "bar"
// create regex expression of case insensitive version of the key string
const regex=passedKey=> new RegExp(`^${passedKey}$`,'gi');
// find the key that matches the string you are passing
const formattedKey=passedKey=>Object.keys(obj).find(key=>regex(passedKey).test(key));
formattedKey('Foo'); // returns foo
formattedKey('FoO'); // returns foo
// consequently you can can use it like wise
obj[formattedKey('Foo')] // returns bar
obj[formattedKey('FoO')] // returns bar
obj[formattedKey('foo')] // returns bar

How to check if JavaScript object is JSON

I have a nested JSON object that I need to loop through, and the value of each key could be a String, JSON array or another JSON object. Depending on the type of object, I need to carry out different operations. Is there any way I can check the type of the object to see if it is a String, JSON object or JSON array?
I tried using typeof and instanceof but both didn't seem to work, as typeof will return an object for both JSON object and array, and instanceof gives an error when I do obj instanceof JSON.
To be more specific, after parsing the JSON into a JS object, is there any way I can check if it is a normal string, or an object with keys and values (from a JSON object), or an array (from a JSON array)?
For example:
JSON
var data = "{'hi':
{'hello':
['hi1','hi2']
},
'hey':'words'
}";
Sample JavaScript
var jsonObj = JSON.parse(data);
var path = ["hi","hello"];
function check(jsonObj, path) {
var parent = jsonObj;
for (var i = 0; i < path.length-1; i++) {
var key = path[i];
if (parent != undefined) {
parent = parent[key];
}
}
if (parent != undefined) {
var endLength = path.length - 1;
var child = parent[path[endLength]];
//if child is a string, add some text
//if child is an object, edit the key/value
//if child is an array, add a new element
//if child does not exist, add a new key/value
}
}
How do I carry out the object checking as shown above?
I'd check the constructor attribute.
e.g.
var stringConstructor = "test".constructor;
var arrayConstructor = [].constructor;
var objectConstructor = ({}).constructor;
function whatIsIt(object) {
if (object === null) {
return "null";
}
if (object === undefined) {
return "undefined";
}
if (object.constructor === stringConstructor) {
return "String";
}
if (object.constructor === arrayConstructor) {
return "Array";
}
if (object.constructor === objectConstructor) {
return "Object";
}
{
return "don't know";
}
}
var testSubjects = ["string", [1,2,3], {foo: "bar"}, 4];
for (var i=0, len = testSubjects.length; i < len; i++) {
alert(whatIsIt(testSubjects[i]));
}
Edit: Added a null check and an undefined check.
You can use Array.isArray to check for arrays. Then typeof obj == 'string', and typeof obj == 'object'.
var s = 'a string', a = [], o = {}, i = 5;
function getType(p) {
if (Array.isArray(p)) return 'array';
else if (typeof p == 'string') return 'string';
else if (p != null && typeof p == 'object') return 'object';
else return 'other';
}
console.log("'s' is " + getType(s));
console.log("'a' is " + getType(a));
console.log("'o' is " + getType(o));
console.log("'i' is " + getType(i));
's' is string'a' is array 'o' is object'i' is other
An JSON object is an object. To check whether a type is an object type, evaluate the constructor property.
function isObject(obj)
{
return obj !== undefined && obj !== null && obj.constructor == Object;
}
The same applies to all other types:
function isArray(obj)
{
return obj !== undefined && obj !== null && obj.constructor == Array;
}
function isBoolean(obj)
{
return obj !== undefined && obj !== null && obj.constructor == Boolean;
}
function isFunction(obj)
{
return obj !== undefined && obj !== null && obj.constructor == Function;
}
function isNumber(obj)
{
return obj !== undefined && obj !== null && obj.constructor == Number;
}
function isString(obj)
{
return obj !== undefined && obj !== null && obj.constructor == String;
}
function isInstanced(obj)
{
if(obj === undefined || obj === null) { return false; }
if(isArray(obj)) { return false; }
if(isBoolean(obj)) { return false; }
if(isFunction(obj)) { return false; }
if(isNumber(obj)) { return false; }
if(isObject(obj)) { return false; }
if(isString(obj)) { return false; }
return true;
}
you can also try to parse the data and then check if you got object:
try {
var testIfJson = JSON.parse(data);
if (typeof testIfJson == "object"){
//Json
} else {
//Not Json
}
}
catch {
return false;
}
If you are trying to check the type of an object after you parse a JSON string, I suggest checking the constructor attribute:
obj.constructor == Array || obj.constructor == String || obj.constructor == Object
This will be a much faster check than typeof or instanceof.
If a JSON library does not return objects constructed with these functions, I would be very suspiciouse of it.
The answer by #PeterWilkinson didn't work for me because a constructor for a "typed" object is customized to the name of that object. I had to work with typeof
function isJson(obj) {
var t = typeof obj;
return ['boolean', 'number', 'string', 'symbol', 'function'].indexOf(t) == -1;
}
You could make your own constructor for JSON parsing:
var JSONObj = function(obj) { $.extend(this, JSON.parse(obj)); }
var test = new JSONObj('{"a": "apple"}');
//{a: "apple"}
Then check instanceof to see if it needed parsing originally
test instanceof JSONObj
I wrote an npm module to solve this problem. It's available here:
object-types: a module for finding what literal types underly objects
Install
npm install --save object-types
Usage
const objectTypes = require('object-types');
objectTypes({});
//=> 'object'
objectTypes([]);
//=> 'array'
objectTypes(new Object(true));
//=> 'boolean'
Take a look, it should solve your exact problem. Let me know if you have any questions! https://github.com/dawsonbotsford/object-types
Why not check Number - a bit shorter and works in IE/Chrome/FF/node.js
function whatIsIt(object) {
if (object === null) {
return "null";
}
else if (object === undefined) {
return "undefined";
}
if (object.constructor.name) {
return object.constructor.name;
}
else { // last chance 4 IE: "\nfunction Number() {\n [native code]\n}\n" / node.js: "function String() { [native code] }"
var name = object.constructor.toString().split(' ');
if (name && name.length > 1) {
name = name[1];
return name.substr(0, name.indexOf('('));
}
else { // unreachable now(?)
return "don't know";
}
}
}
var testSubjects = ["string", [1,2,3], {foo: "bar"}, 4];
// Test all options
console.log(whatIsIt(null));
console.log(whatIsIt());
for (var i=0, len = testSubjects.length; i < len; i++) {
console.log(whatIsIt(testSubjects[i]));
}
I combine the typeof operator with a check of the constructor attribute (by Peter):
var typeOf = function(object) {
var firstShot = typeof object;
if (firstShot !== 'object') {
return firstShot;
}
else if (object.constructor === [].constructor) {
return 'array';
}
else if (object.constructor === {}.constructor) {
return 'object';
}
else if (object === null) {
return 'null';
}
else {
return 'don\'t know';
}
}
// Test
var testSubjects = [true, false, 1, 2.3, 'string', [4,5,6], {foo: 'bar'}, null, undefined];
console.log(['typeOf()', 'input parameter'].join('\t'))
console.log(new Array(28).join('-'));
testSubjects.map(function(testSubject){
console.log([typeOf(testSubject), JSON.stringify(testSubject)].join('\t\t'));
});
Result:
typeOf() input parameter
---------------------------
boolean true
boolean false
number 1
number 2.3
string "string"
array [4,5,6]
object {"foo":"bar"}
null null
undefined
I know this is a very old question with good answers. However, it seems that it's still possible to add my 2ยข to it.
Assuming that you're trying to test not a JSON object itself but a String that is formatted as a JSON (which seems to be the case in your var data), you could use the following function that returns a boolean (is or is not a 'JSON'):
function isJsonString( jsonString ) {
// This function below ('printError') can be used to print details about the error, if any.
// Please, refer to the original article (see the end of this post)
// for more details. I suppressed details to keep the code clean.
//
let printError = function(error, explicit) {
console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}
try {
JSON.parse( jsonString );
return true; // It's a valid JSON format
} catch (e) {
return false; // It's not a valid JSON format
}
}
Here are some examples of using the function above:
console.log('\n1 -----------------');
let j = "abc";
console.log( j, isJsonString(j) );
console.log('\n2 -----------------');
j = `{"abc": "def"}`;
console.log( j, isJsonString(j) );
console.log('\n3 -----------------');
j = '{"abc": "def}';
console.log( j, isJsonString(j) );
console.log('\n4 -----------------');
j = '{}';
console.log( j, isJsonString(j) );
console.log('\n5 -----------------');
j = '[{}]';
console.log( j, isJsonString(j) );
console.log('\n6 -----------------');
j = '[{},]';
console.log( j, isJsonString(j) );
console.log('\n7 -----------------');
j = '[{"a":1, "b": 2}, {"c":3}]';
console.log( j, isJsonString(j) );
When you run the code above, you will get the following results:
1 -----------------
abc false
2 -----------------
{"abc": "def"} true
3 -----------------
{"abc": "def} false
4 -----------------
{} true
5 -----------------
[{}] true
6 -----------------
[{},] false
7 -----------------
[{"a":1, "b": 2}, {"c":3}] true
Please, try the snippet below and let us know if this works for you. :)
IMPORTANT: the function presented in this post was adapted from https://airbrake.io/blog/javascript-error-handling/syntaxerror-json-parse-bad-parsing where you can find more and interesting details about the JSON.parse() function.
function isJsonString( jsonString ) {
let printError = function(error, explicit) {
console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}
try {
JSON.parse( jsonString );
return true; // It's a valid JSON format
} catch (e) {
return false; // It's not a valid JSON format
}
}
console.log('\n1 -----------------');
let j = "abc";
console.log( j, isJsonString(j) );
console.log('\n2 -----------------');
j = `{"abc": "def"}`;
console.log( j, isJsonString(j) );
console.log('\n3 -----------------');
j = '{"abc": "def}';
console.log( j, isJsonString(j) );
console.log('\n4 -----------------');
j = '{}';
console.log( j, isJsonString(j) );
console.log('\n5 -----------------');
j = '[{}]';
console.log( j, isJsonString(j) );
console.log('\n6 -----------------');
j = '[{},]';
console.log( j, isJsonString(j) );
console.log('\n7 -----------------');
j = '[{"a":1, "b": 2}, {"c":3}]';
console.log( j, isJsonString(j) );
Try, Catch block will help you to solve this
Make a function
function IsJson(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
Example:
console.log(IsJson('abc')) // false
console.log(IsJson('[{"type":"email","detail":"john#example.com"}]')) // true
Try this
if ( typeof is_json != "function" )
function is_json( _obj )
{
var _has_keys = 0 ;
for( var _pr in _obj )
{
if ( _obj.hasOwnProperty( _pr ) && !( /^\d+$/.test( _pr ) ) )
{
_has_keys = 1 ;
break ;
}
}
return ( _has_keys && _obj.constructor == Object && _obj.constructor != Array ) ? 1 : 0 ;
}
It works for the example below
var _a = { "name" : "me",
"surname" : "I",
"nickname" : {
"first" : "wow",
"second" : "super",
"morelevel" : {
"3level1" : 1,
"3level2" : 2,
"3level3" : 3
}
}
} ;
var _b = [ "name", "surname", "nickname" ] ;
var _c = "abcdefg" ;
console.log( is_json( _a ) );
console.log( is_json( _b ) );
console.log( is_json( _c ) );
Based on #Martin Wantke answer, but with some recommended improvements/adjusts...
// NOTE: Check JavaScript type. By Questor
function getJSType(valToChk) {
function isUndefined(valToChk) { return valToChk === undefined; }
function isNull(valToChk) { return valToChk === null; }
function isArray(valToChk) { return valToChk.constructor == Array; }
function isBoolean(valToChk) { return valToChk.constructor == Boolean; }
function isFunction(valToChk) { return valToChk.constructor == Function; }
function isNumber(valToChk) { return valToChk.constructor == Number; }
function isString(valToChk) { return valToChk.constructor == String; }
function isObject(valToChk) { return valToChk.constructor == Object; }
if(isUndefined(valToChk)) { return "undefined"; }
if(isNull(valToChk)) { return "null"; }
if(isArray(valToChk)) { return "array"; }
if(isBoolean(valToChk)) { return "boolean"; }
if(isFunction(valToChk)) { return "function"; }
if(isNumber(valToChk)) { return "number"; }
if(isString(valToChk)) { return "string"; }
if(isObject(valToChk)) { return "object"; }
}
NOTE: I found this approach very didactic, so I submitted this answer.
Peter's answer with an additional check! Of course, not 100% guaranteed!
var isJson = false;
outPutValue = ""
var objectConstructor = {}.constructor;
if(jsonToCheck.constructor === objectConstructor){
outPutValue = JSON.stringify(jsonToCheck);
try{
JSON.parse(outPutValue);
isJson = true;
}catch(err){
isJson = false;
}
}
if(isJson){
alert("Is json |" + JSON.stringify(jsonToCheck) + "|");
}else{
alert("Is other!");
}
I have a pretty lazy answer to this, which will not throw an error if you try to parse a string/other values.
const checkForJson = (value) => {
if (typeof value !== "string") return false;
return value[0] === "{" && value[value.length - 1] === "}";
}
You can use this to check the value of your keys while you make some recursive func; sorry if this doesn't answer the question completely
Ofc this isn't the most elegant solution and will fail when a string actually starts with "{" and ends with "}" although those use cases would be rare, and if you really wanted, you can check for a presence of quotes or other nonsense... anyway, use at your own discretion.
TLDR: it's not bulletproof, but it's simple and works for the vast majority of use cases.
lodash is also the best bet to check these things.
function Foo() {
this.a = 1;
}
_.isPlainObject(new Foo);
// => false
_.isPlainObject([1, 2, 3]);
// => false
_.isPlainObject({ 'x': 0, 'y': 0 });
// => true
_.isPlainObject(Object.create(null));
// => true
https://www.npmjs.com/package/lodash
https://lodash.com/docs/#isPlainObject
Quickly check for a JSON structure using lodash-contrib:
const _ = require('lodash-contrib');
_.isJSON('{"car": "ferarri"}'); //true for stringified
_.isJSON({car: "ferarri"}); //true
Usage guide: this blog entry
try this dirty way
('' + obj).includes('{')

In javascript how can I dynamically get a nested property of an object

var arr = { foo : 1, bar: { baz : 2 }, bee : 3 }
function getter(variable) {
return arr[variable];
}
If I want 'foo' vs 'bee' I can just do arr[variable] - that's easy, and the function does that.
But what if I want to get arr.bar.baz AKA arr[bar][baz]?
What can I pass to the getter function that will let me do that, (and of course also let me get non-nested properties using the same function).
I tried getter('bar.baz') and getter('[bar][baz]') but those didn't work.
I suppose I can parse for dots or brackets (like here: In javascript, test for property deeply nested in object graph?). Is there a cleaner way? (Besides eval of course.)
Especially because I need to get the deeply set properly many many times in a loop for a bunch of array elements.
You can use a deep access function based on a string for the path. Note that you can't have any periods in the property names.
function getPropByString(obj, propString) {
if (!propString)
return obj;
var prop, props = propString.split('.');
for (var i = 0, iLen = props.length - 1; i < iLen; i++) {
prop = props[i];
var candidate = obj[prop];
if (candidate !== undefined) {
obj = candidate;
} else {
break;
}
}
return obj[props[i]];
}
var obj = {
foo: {
bar: {
baz: 'x'
}
}
};
console.log(getPropByString(obj, 'foo.bar.baz')); // x
console.log(getPropByString(obj, 'foo.bar.baz.buk')); // undefined
If the access string is empty, returns the object. Otherwise, keeps going along access path until second last accessor. If that's an ojbect, returns the last object[accessor] value. Otherwise, returns undefined.
Using ES6:
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var {foo, bar, bar: {baz}, bee} = arr;
Same as:
// var foo = 1;
// var bar = {baz: 2};
// var baz = 2;
// var bee = 3;
Using lodash:
https://lodash.com/docs#get
_.get(arr, 'bar.baz'); //returns 2;
_.get(arr, 'bar.baz[5].bazzz'); //returns undefined wont throw error;
_.get(arr, 'bar.baz[5].bazzz', 'defaultvalue'); // Returns defaultValue because result is undefined
A recursive way :
function getValue(obj, path) {
if (!path) return obj;
const properties = path.split('.');
return getValue(obj[properties.shift()], properties.join('.'))
}
const myObj = {
foo: {
bar: {
value: 'good'
}
}
}
console.log(getValue(myObj, 'foo.bar.value')); // good
How about change the getter function signature as getter('bar', 'baz') instead
function getter() {
var v = arr;
for(var i=0; i< arguments.length; i++) {
if(!v) return null;
v = v[arguments[i]];
}
return v;
}
ps. didn't test, but you get the idea ;)
A one liner for you:
const mock = {
target: {
"prop1": {
"prop2": {
"prop3": "sad"
}
}
},
path: "prop1.prop2.prop3",
newValue: "happy"
};
mock.path.split(".").reduce(
(acc, curr, i, src) =>
(curr === src[src.length - 1]) ? acc[src[src.length - 1]] = mock.newValue : acc[curr], mock.target);
console.log(mock.target); //? { prop1: { prop2: { prop3: 'happy' } } }
Here's a very simple one liner which grants you dynamic access via "foo.bar.baz" mechanism,
var obj = {
foo: {
bar: {
baz: 'foobarbaz'
}
}
}
const nestedAccess = "foo.bar.baz";
console.log(nestedAccess.split('.').reduce((prev, cur) => prev[cur], obj)) //'foobarbaz'
I have recently developed my own Object method to get an object property nested among objects and arrays regardless how deep it is. It utilizes a single line of recursive approach. Check this out.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = { foo : 1, bar: { baz : 2 }, bee : 3 },
bazval = myObj.getNestedValue("bar","baz");
document.write(bazval);
Now let's check a deeper nested array object combo data structure
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myArr = [{fox: [{turn:[857, 432]}]}, {sax: [{pana:[777, 987]}]}, {ton: [{joni:[123, 567]}]}, {piu: [{burn:[666, 37]}]}, {sia: [{foxy:[404, 696]}]}];
document.write(myArr.getNestedValue(3,"piu",0,"burn",1));
I believe being able to pass search parameters dynamically to existing array methods would make actions like searching, filtering or replacing of deeply nested structures much easy.
Using reduce we can fetch the value in single line of code.
const testobj = {b:{c:'1', d:{e:'2',f:'3'}}, g:{h:'3'}}
function fetchByDotOperator(object, value) {
return value.split('.').reduce((acc, curr) => acc[curr], object);
}
console.log(fetchByDotOperator(testobj,'b.d.e'))
You can access the functions arguments where you can pass any number of strings.
I also recommend using arr as a parameter for better encapsulation:
function getter() {
var current = arguments[0];
for(var i = 1; i < arguments.length; i++) {
if(current[arguments[i]]) {
current = current[arguments[i]];
} else {
return null;
}
}
return current;
}
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var baz = getter(arr, 'bar', 'baz');
function getPropertyByString(object, propString) {
let value = object;
const props = propString.split('.');
for (let index = 0; index < props.length; index += 1) {
if (props[index] === undefined) break;
value = value[props[index]];
}
return value;
};
const object = {
name: 'any_name',
address: {
number: 77,
test: {
name: 'test'
}
}
}
console.log(getPropertyByString(object, 'address.test.name'))
// test
Above answers help you access nested objects only, however you might also want to access data in an object/array data type. You can try this recusive method:
const getValue = (obj, key) => {
const keyParts = key.split(".");
return getValueHelper(obj, keyParts);
};
const getValueHelper = (obj, keyParts) => {
if (keyParts.length == 0) return obj;
let key = keyParts.shift();
if (Array.isArray(obj[key])) {
return obj[key].map((x) => getValueHelper(x, [...keyParts])).flat();
}
return getValueHelper(obj[key], [...keyParts]);
};
//Examples
let data1 = {
a: [{ b: { c: [{ d: [{ e: 1 }] }] } }, { b: { c: [{ d: [{ e: 2 }] }] } }],
};
console.log(getValue(data1, "a.b.c.d.e"));
//Output
//[ 1, 2 ]
let data2 = {
a:{b:1},
};
console.log(getValue(data2, "a.b"));
//Output
//1
p.s. Remove .flat() to get desired output for arrays.
Theres a function defined on this blog to safely read nested properties from a JS object
It allows you to mine an object for properties... ie.
safeRead(arr, 'foo', 'bar', 'baz');
and if any part of the object chain is null or undefined it returns an empty string....
let obj = {foo : {bar: {baz:1}}};
// -- simply
console.log(eval('obj.foo.bar.baz')); //-- 1
// -- safer
val = "";
try {
val = eval('Obj.foo.bar.baz')
}
catch(e) {
val = "empty"
}
// -- val = 1
// -- use at your risk ;)
Here I created a small suite of functions to 'get / 'set' / 'push' / 'pull' from object nested properties.
inputObject : Target object.
Ex: obj = {a:1, b:{c:2,d:3}}
propertyString : String containing the key to access.
Ex: "b.c"
Finally:
_getObjectValueByPathString(obj, "b.c") would return 2
function _getObjectValueByPathString(inputObject, propertyString) {
let splitStr = propertyString.split('.');
if (!inputObject.hasOwnProperty(splitStr[0])) return undefined;
if (splitStr.length === 1) {
return inputObject[splitStr[0]];
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
return _getObjectValueByPathString(inputObject[firstValue], newPropertyString);
}
else {
throw "Invalid property string provided";
}
}
function _setObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]] = inputValue;
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_setObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}
function _pushObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]].push(inputValue);
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_pushObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}
function _pullObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]].pull(inputValue);
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_pullObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}

Categories

Resources