SachDan SachDan - 1 month ago 13
JSON Question

How to get the JSON path from element

I have implemented a recursive javascript function to draw the following structure in an svg by reading a JSON schema as follows.

Is there a way to determine the JSON path of a selected node? Like, when the user clicks on a node (say

country:string
), is there a way to return the JSON path (as
person.address.country
)?

UPDATE: I'm using backbone models, so the solution in my mind is to keep track and add JSON path as an attribute of the node to later use. I'm looking for other direct methods.

{
"title": "person",
"type": "object",
"properties": {
"first name": { "type": "string" },
"last name": { "type": "string" },
"age":{ "type":"number"},
"birthday": { "type": "string", "format": "date-time" },
"address": {
"type": "object",
"properties": {
"street address": {
"type": "object",
"properties": {
"house number": { "type": "number" },
"lane": { "type": "string" }
}
},
"city": { "type": "string" },
"state": { "type": "string" },
"country": { "type" : "string" }
}
},
"phone number": {
"type": "array",
"items": {
"type": "object",
"properties": {
"location": {
"type": "string"
},
"code": {
"type": "number"
}
},
"required": [
"location",
"code"
]
}
},
"children": {
"type": "array",
"items": {
"type": "string"
}
},
"nickname":{"type":"string"}
}
}


enter image description here

The recursive code(backbone function) is as follows:

//initiate
var title = data.title || "Root";
var count = this.traverseJSONSchema(data, title, 0, 0, this.get('tempParent'));

//function
traverseJSONSchema: function (root, rootName, level, rank, resultPane) {
var height = this.nodeHeight,
width = this.containerWidth,
margin = width / 6,
x = 0,
overhead = rank * margin,
y = level * height;
var tempParent = resultPane.append("g").attr("class", "nested-group");
if (root.type === "object") {
if (rootName !== "") {
var nodeText = rootName + ":" + root.type;
var node = new DataMapper.Models.Node({parent: tempParent, text: nodeText, x: x, y: y, type: this.get('type'), category: "object", height: height, width: width});
node.drawContainerNode(overhead);
rank++;
level++;
}
var nestedParent = tempParent.append("g").attr("class", "nested-group");
var keys = root.properties; //select PROPERTIES
for (var i = 0; i < Object.keys(keys).length; i++) { //traverse through each PROPERTY of the object
var keyName = Object.keys(keys)[i];
var key = keys[keyName];
level = this.traverseJSONSchema(key, keyName, level, rank, tempParent);
}

} else if (root.type === "array") {
var keys = root.items; //select ITEMS
if (rootName !== "") {
var nodeText = rootName + ":" + root.type + "[" + keys.type + "]";
var node = new DataMapper.Models.Node({parent: tempParent, text: nodeText, x: x, y: y, type: this.get('type'), category: "array", height: height, width: width});
node.drawContainerNode(overhead);
rank++;
level++;
}

level = this.traverseJSONSchema(keys, "", level, rank, tempParent); //recurse through the items of array
} else if (["string", "integer", "number", "boolean"].indexOf(root.type) > -1) { //when the type is a primitive
tempParent.classed("nested-group", false);
if (rootName !== "") {
var nodeText = rootName + ":" + root.type;
var node = new DataMapper.Models.Node({parent: tempParent, text: nodeText, x: x, y: y, type: this.get('type'), category: root.type, height: height, width: width});
node.drawContainerNode(overhead);
rank++;
level++;
}
}
return level;
}

Answer

You could use a special treatment for concatination the path.

function getPath(object, search) {
    function iter(o, p) {
        return Object.keys(o).some(function (k) {
            if (k === key && o[k] && o[k].type === value) {
                path = p.concat(k).join('.');
                return true;
            }
            if (o[k] !== null && typeof o[k] === 'object') {
                return iter(o[k],
                    k === 'properties' && !o.title ?
                        p :
                        p.concat(k === 'properties' && o.title ? o.title : k)
                );
            }
        });
    }

    var parts = search.split(':'),
        key = parts[0],
        value = parts[1],
        path;

    iter(object, []);
    return path;
}

var data = { title: "person", type: "object", properties: { "first name": { type: "string" }, "last name": { type: "string" }, age: { type: "number" }, birthday: { type: "string", format: "date-time" }, address: { type: "object", properties: { "street address": { type: "object", properties: { "house number": { type: "number" }, lane: { type: "string" } } }, city: { type: "string" }, state: { type: "string" }, country: { type: "string" } } }, "phone number": { type: "array", items: { type: "object", properties: { location: { type: "string" }, code: { type: "number" } }, required: ["location", "code"] } }, children: { type: "array", items: { type: "string" } }, nickname: { type: "string" } } };

console.log(getPath(data, 'country:string'));
.as-console-wrapper { max-height: 100% !important; top: 0; }