Is there any way to use JSON pointers to use relative path in side JSON string?

2.5k Views Asked by At

Example : Currently we can refer name property value in address name property as below

{
        person: { 
            name: "a",
            address: {
                name: { "$ref":"#/person/name"},
                zip:"123"
            }
        }
  }

Is there any way to refer same with relative path like below.

{
        person: { 
            name: "a",
            address: {
                name: { "$ref":"#/../name"},
                zip:"123"
            }
        }
  }

With Above question what I am looking at is an easy way to refer any value in current json where there are complex and multiple hierarchies as given below in first code snippet. For referring “Name” property from sibling I have to mention a complete path right from the root, which can be though to maintain. If there is small change in hierarchy structure the reference will not be valid anymore.

{
  "one": {
    "two": {
      "three": {
        "four": {
          "five": {
            "fiveLevel1": {
              "name": "foo"
            },
            "fiveLevel2": {
              "name": {
                "$ref": "#/one/two/three/four/five/fiveLevel1/name"
              }
            }
          }
        }
      }
    }
  }
}

If we could able to refer the same property as given in second snippet then change in upper hierarchy will not have any impact on reference, There will be only change when there is direct change in the sibling of “FiveLevel1” and “FiveLevel2”

{
  "one": {
    "two": {
      "three": {
        "four": {
          "five": {
            "fiveLevel1": {
              "name": "foo"
            },
            "fiveLevel2": {
              "name": {
                "$ref": "#../fiveLevel1/name"
              }
            }
          }
        }
      }
    }
  }
} 
2

There are 2 best solutions below

0
On

This feature doesn't exist in vanilla JavaScript, as well stated in the comments.

However, nothing stops you from adding circular references to your objects, so that you can achieve something close to what you're looking for. You could even wrap this up in a class:

class RelPathObj {
  constructor(obj) {
    function build (parent, parentKey, obj) {
      if (typeof obj === "object") {
        for (let key in obj) {
          if (typeof obj[key] === "object") {
            parent[key] = { "..": parent, ...obj[key] };
            build(parent[key], key, obj[key]);
          } else {
            if (key === "$ref") {
              const path = obj[key].split("/");
              Object.defineProperty(parent[".."],
                                    parentKey,
                                    {
                                      get: function() {
                                        let value = parent[".."];
                                        path.forEach(p => { value = value[p] });
                                        return value;
                                      },
                                      set: function(value) {
                                        let ref = parent[".."];
                                        path.forEach(p => { 
                                          if (typeof ref[p] === "object")
                                            ref = ref[p] 
                                          else
                                            ref[p] = value
                                        });
                                      }
                                    });
            } else {
              parent[key] = obj[key];
            }
          }
        }
      } else {
        parent = obj;
      }
    }
    this.root = {};
    build(this.root, "root", obj);
  } 
}

const relativePathObject = new RelPathObj({
  "one": {
    "two": {
      "three": {
        "four": {
          "five": {
            "fiveLevel1": {
              "name": "foo"
            },
            "fiveLevel2": {
              "name": {
                "$ref": "../fiveLevel1/name"
              }
            }
          }
        }
      }
    }
  }
});

let fiveLevel1 = relativePathObject["root"]["one"]["two"]["three"]["four"]["five"]["fiveLevel1"];
console.log(fiveLevel1.name);
  
let fiveLevel2 = fiveLevel1[".."]["fiveLevel2"];
console.log(fiveLevel2.name);
  
fiveLevel1.name = "bar";
console.log(fiveLevel2.name);
  
fiveLevel2.name = "baz"
console.log(fiveLevel1.name);

It's far, far from being perfect, but the main idea here is to loop through the object's properties adding a reference to the parent inside every child, and then repeating this recursively. When it finds $ref, it parses the path and translates it to its value.

Of course, if the object hierarchy changes, you'd then have to fix the parent references (it's something you could implement in your custom class).

Remember: this is just a simple code snippet to show you an idea of what you could do, I didn't put a lot of thought in it. Don't use it in production.

0
On

There is a draft Relative JSON Pointers which unfortunately exipred in the Aug, 2021 and I am not sure if there is any later extension.

Nevertheless, it should be a good start as it is an extension of the already mentioned RFC6901 - JavaScript Object Notation (JSON) Pointer