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 browse all

Nested Enums in TypeScript. | cover picture
Nested Enums in TypeScript.
Created August 19 2021.
How to solve the need of nested and multi-level Enums in TypeScript.
WebAssembly + Emscripten Notes. | cover picture
WebAssembly + Emscripten Notes.
Created December 25 2020, updated August 19 2021.
My notes on learning WebAssembly and its integration with Emscripten.
Docker Notes. | cover picture
Docker Notes.
Created November 19 2020, updated December 25 2020.
My notes on learning Docker.
×