15

How would you get a JSON path to a given child node of an object?

E.g.:

var data = {
    key1: {
        children: {
            key2:'value',
            key3:'value',
            key4: { ... }
        }, 
    key5: 'value'
}

An variable with a reference to key4 is given. Now I'm looking for the absolute path:

data.key1.children.key4

Is there any way to get this done in JS?

Thank you in advance.

user1138959
  • 283
  • 2
  • 3
  • 7
  • If your search variable points to a string object (like in the example) you'll not be able to reliably search for that path as (for example) `data.key1.children.key3 === data.key4` would also be `true`, and probably not what you're trying to achieve. – Yoshi Jan 09 '12 at 15:48
  • Thanks Yoshi, I have updated the code sample. Now the reference is pointing to another object. – user1138959 Jan 09 '12 at 15:53

6 Answers6

13

So you have a variable with the value "key3", and you want to know how to access this property dynamically, based on the value of this string?

var str = "key3";
data["key1"]["children"][str];

EDIT

Wow, I can't believe I got this on the first try. There might be some bugs in it, but it works for your test case

LIVE DEMO

var x = data.key1.children.key4;

var path = "data";
function search(path, obj, target) {
    for (var k in obj) {
        if (obj.hasOwnProperty(k))
            if (obj[k] === target)
                return path + "['" + k + "']"
            else if (typeof obj[k] === "object") {
                var result = search(path + "['" + k + "']", obj[k], target);
                if (result)
                    return result;
            }
    }
    return false;
}

var path = search(path, data, x);
console.log(path); //data['key1']['children']['key4']
Rodik
  • 4,054
  • 26
  • 49
Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • 1
    Hi Adam, thanks for your reply. I'm looking for the full path. "key1" and "children" are unknown. I have a reference to data.key1.children.key3 stored in a variable and need to know how to get there. – user1138959 Jan 09 '12 at 15:36
  • @user1138959 - I'm not sure that's possible. I mean, you could do a recursive search through every member of your object, but there'd still be no way to know which member your variable pointed to if you had to values that were the same – Adam Rackis Jan 09 '12 at 15:44
  • 1
    I have updated my code example. I'm looking for an object, not an string value. How would I recursively search through every member and get the path to the matching child? – user1138959 Jan 09 '12 at 16:04
  • @user1138959 - as lwburk mentions, just make sure you don't have something like `children: { ... key6: data.key1` - well, don't do that unless you really like infinite loops ;) – Adam Rackis Jan 09 '12 at 16:31
10

This is the way i have done this.

/**
 * Converts a string path to a value that is existing in a json object.
 * 
 * @param {Object} jsonData Json data to use for searching the value.
 * @param {Object} path the path to use to find the value.
 * @returns {valueOfThePath|null}
 */
function jsonPathToValue(jsonData, path) {
    if (!(jsonData instanceof Object) || typeof (path) === "undefined") {
        throw "Not valid argument:jsonData:" + jsonData + ", path:" + path;
    }
    path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    path = path.replace(/^\./, ''); // strip a leading dot
    var pathArray = path.split('.');
    for (var i = 0, n = pathArray.length; i < n; ++i) {
        var key = pathArray[i];
        if (key in jsonData) {
            if (jsonData[key] !== null) {
                jsonData = jsonData[key];
            } else {
                return null;
            }
        } else {
            return key;
        }
    }
    return jsonData;
}  

For testing,

var obj = {d1:{d2:"a",d3:{d4:"b",d5:{d6:"c"}}}};
jsonPathToValue(obj, "d1.d2"); // a 
jsonPathToValue(obj, "d1.d3"); // {d4: "b", d5: Object}
jsonPathToValue(obj, "d1.d3.d4"); // b
jsonPathToValue(obj, "d1.d3.d5"); // {d6: "c"}
jsonPathToValue(obj, "d1.d3.d5.d6"); // c

Hope that will help someone.

Georgios Syngouroglou
  • 18,813
  • 9
  • 90
  • 92
  • Thanks for this code, exactly what I was looking for, but it there's some littles mistakes : It won't return `undefined`, but `null` or a key from path if that key is not found. Just replace `return null` and `return key` to be good ! – Massale Jul 09 '21 at 11:14
1

My solution:

  • depth first search based on a given reference
  • uses recursion
  • handles arrays

(Result can be retrieved using this deep_value function.)

var key4Ref = { abc: 123 }

var data = {
    key1: {
        children: {
            key2:'value',
            key3:'value',
            key4: key4Ref
        }, 
        key5: 'value'
    }
}

// find the path to a 'ref' within an object 'data'.
const pathTo = (ref, data, path = []) => {
  const found = data && Object.entries(data).find(([k,v]) => {
    if (v === ref) return path.push(k)
    if (typeof v === 'object') {
      const tmp = pathTo(ref, v, [...path, k])
      if (tmp) return path = tmp
    }
  })
  if (found) return path
}

console.log(pathTo(key4Ref, data).join('.'))
Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
0

var data = {
  // Your data is here
  a: {
    b: {
      c: {
        d: "Assalamu alal muslimin"
      }
    }
  },
  // Your function is here
  take: function(path) {
    var temp = this; // take a copy of object
    if(!path) return temp; // if path is undefined or empty return the copy
    path = path.split("/");
    for(var p in path) {
      if(!path[p]) continue; // means "a/" = "a"
      temp = temp[path[p]]; // new data is subdata of data
      if(!temp) return temp; 
    }
    return temp;
  }
};
<input placeholder="Please enter the path"/>
<button onclick="document.querySelector('div').innerText = JSON.stringify(data.take(document.querySelector('input').value))">
  Try it
</button>
<br><br>
Data: {a:{b:{c:{d:"Assalamu alal muslimin"}}}}
<br><br>
Code: data.take(path)
<br><br>
Result:
<div></div>

Shortly the function is :

function getDataByPath(data, path) {
    if(!path) return data; // if path is undefined or empty return data
    path = path.split("/");
    for(var p in path) {
        if(!path[p]) continue; // "a/" = "a"
      . data = data[path[p]]; // new data is subdata of data
        if(!data) return data; // "a/b/d" = undefined
    }
    return data;
}

And the shortest function but it may give errors if you entered wrong path:

function getDataByPath(data, path) {
  for(var i in path.split("/")) data = data[path[i]];
  return data;
}
0

let x;
try{
  x = JSON.parse(prompt("Input your JSON"))
}
catch(e) {
   alert("not a valid json input")
}
var res = {};
var constructResultCurry = function(src){ return constructResult(res,src); }
        
function constructResult(target, src) {
  if(!src) return;
  target[src.key] = src.val;
}
        
function buildPath(key, obj, overAllKey) {
  overAllKey += (overAllKey ? "." : "") + key;
  if(typeof obj[key] != "object") return { key : overAllKey, val : obj[key] };
  Object.keys(obj[key]).forEach(function(keyInner) {
     constructResultCurry(buildPath(keyInner, obj[key], overAllKey));  
  });
}
        
Object.keys(x).forEach(function(k){
  constructResultCurry(buildPath(k, x, ""));
});
console.log("**************ALL FIELDS****************")
console.log(res);
console.log("******************************************")

let conf = confirm("do you need a specific field from JSON");
if ( conf )
{
   let field = prompt("Input field name")
   let results = Object.fromEntries(
  Object.entries(res).filter(([key]) => (key.toLowerCase()).includes((field.toLowerCase()))))
  prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(results));
   console.log(results)
   
} 
else {
   prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(res));
}

The above solution returns the whole json with a full path to every field in it. And also the path of specific field that was requested.

jack
  • 81
  • 6
0

I'm also trying to solve this problem, the JSON path also include array, and this is the method I finally came up with:

function findValuePath(obj, value) {
    // Initialize the array of results and the array of paths
    let result = [];
    let path = [];

    // Recursive functions look up values
    function searchValue(obj, value) {
        for (let key in obj) {
            // If the current attribute value is equal to the target value, the path is logged
            if (obj[key] === value) {
                path.push((Array.isArray(obj) ? `[${key}]` : `.${key}`));
                result = path.slice();
                path.pop();
            }
            // If the current property is an object or array, search recursively
            else if (typeof obj[key] === 'object') {
                path.push((Array.isArray(obj) ? `[${key}]` : `.${key}`));
                searchValue(obj[key], value);
                path.pop();
            }
        }
    }

    // Call the recursive function
    searchValue(obj, value);

    //If the target value is found, the path string is returned, otherwise an empty string is returned
    return result.length > 0 ? result.join('') : '';
}

This is the test example I made:

let obj = {
    a:1,
    b:"hello",
    c:{
        a:"target000",
        b:"tar",
        c:"target_w",
        d:[
            "target0",
            "target1",
            "target2",
            {
                a:2,
                b:"target"
            }
        ]
    }
}
let res = findValuePath(obj,"target")
console.log(res)                   // ".c.d[3].b"
console.log(`obj${res}`)           // "obj.c.d[3].b"
console.log(eval(`obj${res}`))     // "target"
Qiluo
  • 97
  • 1
  • 2