Javascript/JSON get path to given subnode?
Asked Answered
A

7

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.

Apothecium answered 9/1, 2012 at 15:16 Comment(2)
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.Pryor
Thanks Yoshi, I have updated the code sample. Now the reference is pointing to another object.Apothecium
R
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']
Rathe answered 9/1, 2012 at 15:24 Comment(4)
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.Apothecium
@Apothecium - 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 sameRathe
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?Apothecium
@Apothecium - 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 ;)Rathe
K
11

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.

Kaka answered 14/1, 2016 at 19:20 Comment(1)
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 !Orris
G
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('.'))
Guttural answered 5/11, 2020 at 11:58 Comment(0)
A
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;
}
Allonge answered 13/6, 2020 at 14:46 Comment(0)
I
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.

Immunize answered 30/1, 2023 at 1:50 Comment(0)
L
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"
Landsknecht answered 11/3, 2023 at 20:8 Comment(0)
M
0

Adam Rackis answer was very helpful for me, I had to slightly alter it for my purposes, I needed to find every path with a value "DOWN".

var data = {
    key1: {
        children: {
            key2: 'value',
            key3: 'value',
            key4: { a: "DOWN" }
        },
        key5: 'DOWN'
    }
};
  
function findJsonPathsWithString(path, object, target) {
  var matches = [];
  for (var key in object) {
    if (object.hasOwnProperty(key)) {
      if (object[key] === target) {
        matches.push(path + key);
      } else if (typeof object[key] === 'object') {
        var result = this.findJsonPathsWithString(path +  key + '.', object[key], target);
        if (result.length) {
          matches = matches.concat(result);
        }
      }
    }
  }
  return matches;
}
  
var path = findJsonPathsWithString('', data, 'DOWN');
console.log(path); //["key1.children.key4.a","key1.key5"]
Mapping answered 26/1 at 23:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.