JavaScript: how to find a key-value pair in a nested object.

How to recursively traverse a JavaScript nested and unordered object to find if a value exists at a specific key.

JavaScript Logo
JavaScript Logo

emoji 👉 Use case

I have a nested and complex JavaScript object, probably a back-end response or a complex UI state-tree. I can't ask the back-end to change this object. I need to find out if in this object, at any level of his complex and unordered hierarchy (which could be composed by simple values, other objects, arrays, arrays of objects, you name it...) there is at least one key-value pair of my choice.

Take the object below as an example:

//The back-end gives me a nested object with data in an unordered array representing all the features a user has.
let response = {
	data: [
		{
			id: 3,
			label: "CMS",
			features: [],
		},
		{
			id: 1,
			label: "Admin",
			features: [
				{
					id: 15,
					label: "Users",
					features: [],
				},
				{
					id: 22,
					label: "Reports",
					features: [],
				},
				{
					id: 25,
					label: "Analytics",
					features: [],
				}
			],
		},
		{
			id: 4,
			label: "Blog",
			features: [],
		},
		{
			id: 12,
			label: "Payment",
			features: [],
		},
		{
			id: 7,
			label: "Navigation",
			features: [],
		},
	]
};

For example I want to check if the user has a feature, so I want to check if in that object there is a key-value pair as id : myFeatureId. In this case I want to find if the specified user has access to the "Reports" feature, so I'll look for a key-value pair as id : 22.

emoji 👾 Now to the code emoji 👾

To accomplish this task I want to create a JavaScript pure function that receives the server response, the key I want to find, and the value that that key has to hold as inputs. I expect this function to return a boolean True it the key-value pair was successfully found, otherwise False.

//The main function.
//
//fn( input, keyOfTheValue, valueToFind ) -> Bool
//
function findKeyValueRecursively( input, keyOfTheValue, valueToFind ){
	
	//Some validation
	if(typeof keyOfTheValue !== "string"){
		throw new Error("Invalid parameter: keyOfTheValue has to be a string.");
	}

	//Accepts arrays of objects.
	if ( Array.isArray(input) ) {
	
		return input.reduce(function( result, element ){
		
			if ( result === true ) {
				return true;
			}
			
			//Recursive call.
			return findKeyValueRecursively( element, keyOfTheValue, valueToFind );
		
		}, false);
	
	}

	//Process objects.
	if ( input !== null && typeof input === 'object' ) { //IMPORTANT: typeof null === 'object', this is a known JavaScript bug [1].
	
		if ( keyOfTheValue in input && input[ keyOfTheValue ] === valueToFind ) { //See [2].
			//We found it!
			return true;
		}
		
		//Check for other nested objects or arrays.
		for ( let key in input ) {
		
			//The hasOwnProperty function is used to exclude properties found in the prototype chain.
			if ( input.hasOwnProperty(key) ) {
			
				//Recursive call: iterates through all the object properties.
				if ( findKeyValueRecursively( input[ key ], keyOfTheValue, valueToFind ) ) {
					return true;
				}
			
			}
		}
	
	}
	
	return false;
	
}

Let's now create some objects to test different possible inputs and use cases:

//Simple object case.
let inputCase = {
	a : 1,
	b : null,
	c : "Hello World!",
	d : "",
	e : false,
	f : undefined,
};
findKeyValueRecursively( inputCase, 'a', 1) === true
findKeyValueRecursively( inputCase, 'a', 2) === false
findKeyValueRecursively( inputCase, 'a', false) === false
findKeyValueRecursively( inputCase, 'a', undefined) === false
findKeyValueRecursively( inputCase, 'b', 1) === false

//Nested object case.
inputCase = {
	a : {
		a : 1,
		b : null,
		c : "Hello World!",
	},
	b : null,
	c : "Hello World!",
	d : {
		a : 2,
		b : 1,
		c : {
			a : false,
			b : null,
		},
	},
	e : false,
	f : undefined,
};
findKeyValueRecursively( inputCase, 'a', 1) === true
findKeyValueRecursively( inputCase, 'a', 2) === true
findKeyValueRecursively( inputCase, 'a', false) === true
findKeyValueRecursively( inputCase, 'a', undefined) === false
findKeyValueRecursively( inputCase, 'b', 1) === true

//Nested objects and arrays case.
inputCase = [
	{
		a : 0,
		b : null,
		c : "Hello World!",
	},
	{
		a : 0,
		b : null,
		c : "Hello World!",
	},
	{
		a : 2,
		b : null,
		c : [
			{
				a : [
					{
					a : false,
					b : [],
					c : "Hello World!",
				}
				],
				b : null,
				c : "Hello World!",
			},
			{
				a : 1,
				b : {
					b : 1,
				},
				c : "Hello World!",
			}
		],
	},
];
findKeyValueRecursively( inputCase, 'a', 1) === true
findKeyValueRecursively( inputCase, 'a', 2) === true
findKeyValueRecursively( inputCase, 'a', false) === true
findKeyValueRecursively( inputCase, 'a', undefined) === false
findKeyValueRecursively( inputCase, 'b', 1) === true

emoji 📔 Footnotes

[1]: typeof null === 'object' is a JavaScript known bug. It has not been fixed during these years because many websites rely on workarounds and expect that weird result, thus in order not to break them it has not been changed. Initially it was stated in the specifications:

11.4.3 The typeof Operator
The production UnaryExpression : typeofUnaryExpression is evaluated as follows:

Table 20
Table 20

[2]: keyOfTheValue in input is needed in order to prevent reading a not existing key as the value undefined (it can break the function if you search for something like a : undefined).

emoji 📚 Further readings

Why is typeof null === “object”?

The hasOwnProperty() Function

The reduce() Function

For updates, insights or suggestions, feel free to post a comment below! emoji 🙂


Responses

Latest from Web Engineering see all

JavaScript: in depth practical explanation on closures. | cover picture
JavaScript: in depth practical explanation on closures.
Created on 21 July 2018.
A deep look at JavaScript closures with explanations and hands-on code examples.
JavaScript: differences between using var, let and const. | cover picture
JavaScript: differences between using var, let and const.
Created on 19 June 2018.
A close look and explanation at how to properly use the new ES6 JavaScript variable declaration keywords.
Browser Rendering Queue in-depth. | cover picture
Browser Rendering Queue in-depth.
Created on 10 January 2018, updated on 17 June 2018.
An in-depth full explanation on how browsers manage to push pixels on the screen, and how to improve performance.
×