1

Since JSON can not perfom functions i need to eval a JSON string by a key flag in the JSON object. I want to mutate the JSON data when it's in Object form.

I can't find a function/method online that can give me the full path to key based on a known key pattern.

Given this data:

  {
    "js:bindto": "#chart-1", // note the key here 'js:'
    "point": {
        "r": 5
    },
    "data": {
        "x": "x",
        "xFormat": "%Y",
        "columns": [
            ...
        ],
        "colors": {
            "company": "#ed1b34",
            "trendline": "#ffffff"
        }
    },
    "legend": {
        "show": false
    },
    "axis": {
        "x": {
            "padding": {
                "left": 0
            },
            "type": "timeseries",
            "tick": {
                "format": "%Y",
                "outer": false
            }
        },
        "y": {
            "tick": {
                "outer": false,
                "js:format": "d3.format(\"$\")" // note the key here 'js:'
            }
        }
    },
    "grid": {
        "lines": {
            "front": false
        },
        "y": {
            "lines": [...]
        }
    }
}

The flags are keys beginning with js:.

If I look up js:format, I'd expect it's path to be something like: /js:bindto and /axis/y/tick/js:format. Open to suggestions.

In context:

mutateGraphData<T>(data:T):T {
   // data here is a parsed JSON string. ( an object as shown above )

    let jsonKeys = this.findKeysInJSON(JSON.stringify(data), "js:");

    // jsonKeys = ["js:bindto","js:format"]
        jsonKeys.map((key:string) => {
          // find the key in data, then modify the value. (stuck here)
         // i need the full path to the key so i can change the data property that has the key in question
        });
    });
    return data;
}

findKeysInJSON<T>(jsonString:string, key:string):Array<T> {
        let keys = [];
        if (Boolean(~(jsonString.indexOf(`"${key}`)))) {
            let regEx = new RegExp(key + "(\\w|\\-)+", "g");
            keys = jsonString.match(regEx);
        }
        return keys;
}

I have been around a few npm packages:

I have looked at

Nothing have I seen can return the full path to the key in question so I can modify it, nor work directly on the object itself so I can change its properties.

Community
  • 1
  • 1
Simon
  • 2,484
  • 6
  • 35
  • 54

3 Answers3

1

You could go with Ramda. It has built in functions that will allow you to map over an object and modify parts of the object in a completely immutable way.

Ramda offers R.lensPath that will allow you to dig into the object, and modify it as needed. It doesn't follow the pattern you want, but we can quickly patch it with the lensify function.

It is using the R.set function to set the node to the passed in value, and creating a pipeline that will run all the operations on the passed in object.

You can do some really cool stuff with ramda and lenses. Checkout evilsoft on livecoding.tv for a really good overview.

const obj={"js:bindto":"#chart-1",point:{r:5},data:{x:"x",xFormat:"%Y",columns:[],colors:{company:"#ed1b34",trendline:"#ffffff"}},legend:{show:!1},axis:{x:{padding:{left:0},type:"timeseries",tick:{format:"%Y",outer:!1}},y:{tick:{outer:!1,"js:format":'d3.format("$")'}}},grid:{lines:{front:!1},y:{lines:[]}}}

const lensify = path => R.lensPath(path.split('/'))
// create the property accessors split by /
const bindToLens = lensify('js:bindto')
const formatLens = lensify('axis/y/tick/js:format')

const modifyObj = R.pipe(
  R.set(bindToLens, 'dis be bind'),
  R.set(formatLens, 'I been here')
)

console.log(modifyObj(obj))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>
synthet1c
  • 6,152
  • 2
  • 24
  • 39
  • @georg watch https://www.livecoding.tv/evilsoft/videos/d5BDO-functional-js-lenses it will take a couple of hours to sink in, but well worth the effort – synthet1c Oct 10 '16 at 12:41
  • Useful. But still on line 6, I'd need to know the full path in order to mutate that property. – Simon Oct 10 '16 at 19:40
0

After doing a look around I modified the answer from Javascript/JSON get path to given subnode? @adam-rackis

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

search(data,"js:format").slice(1); // returns: axis.y.tick.js:format
search(data,"js:bindto").slice(1); // returns: js:bindto

Now I can use dotty or object-resolve-path or plain ole split('.') to resolve the path to the object.

Community
  • 1
  • 1
Simon
  • 2,484
  • 6
  • 35
  • 54
0

With object-scan this should be a one liner. We've been using it a lot for data processing and it's powerful once you wrap your head around it. Here is how you could use it

// const objectScan = require('object-scan');

const find = (data) => objectScan(['**.js:*'], { joined: true })(data);

const data = { 'js:bindto': '#chart-1', point: { r: 5 }, data: { x: 'x', xFormat: '%Y', columns: [], colors: { company: '#ed1b34', trendline: '#ffffff' } }, legend: { show: false }, axis: { x: { padding: { left: 0 }, type: 'timeseries', tick: { format: '%Y', outer: false } }, y: { tick: { outer: false, 'js:format': 'd3.format("$")' } } }, grid: { lines: { front: false }, y: { lines: [] } } };

console.log(find(data));
// => [ 'axis.y.tick.js:format', 'js:bindto' ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24