I have a small issue with the parameter direction of the function getConnectedNodes() based on the Vis.js documentation (search for "getConnectedNodes" in the link)
Any idea to get the direction of the edges using the parameter (i don't know how to)?
JSON Example
[
{ "x": 0, "y": 0, "id": "0", "connections": [ 2 ] // i think here should be a from?},
{ "x": 200, "y": 0, "id": "1", "connections": [ 3, 2 ] },
{ "x": 500, "y": 500, "id": "2", "connections": [ 0, 1 ] },
{ "x": 300, "y": -200, "id": "3", "connections": [ 1 ] }
]
Here part of the code
google.script.run.withSuccessHandler(([nodes, edges]) => new vis.Network(container, {nodes: nodes, edges: edges}, options)).sample();
let network;
function init() {
container = document.getElementById('mynetwork');
exportArea = document.getElementById('input_output');
network = google.script.run.withSuccessHandler(([nodes, edges]) => {network = new vis.Network(container, {nodes: nodes, edges: edges}, options);}).sample();
};
function addConnections(elem, index) {
elem.connections = network.getConnectedNodes(index); < I THINK THE PROBLEM IS HERE
}
function exportNetwork() {
var nodes = objectToArray(network.getPositions());
nodes.forEach(addConnections);
var exportValue = JSON.stringify(nodes, undefined, 2);
exportArea.innerHTML = exportValue;
}
function objectToArray(obj) {
return Object.keys(obj).map(function(key) {
obj[key].id = key;
return obj[key];
});
}
Before hand, thanks a lot!
index is the index of the array like 0, 1, 2,,,. The start index is 0. On the other hand, elem is the object like {x: ###, y: ###, id: ###}. From these situation, I thought that index of getConnectedNodes(index) might be elem.id. So how about the following modification?
From:
elem.connections = network.getConnectedNodes(index);
To:
elem.connections = network.getConnectedNodes(elem.id, "from");
From the document, if you want to retrieve "parent", you can retrieve it by adding from to the argument.
For a node id, returns an array with the id's of the connected nodes.
If optional parameter direction is set to string 'from', only parent nodes are returned.
If direction is set to 'to', only child nodes are returned.
Any other value or undefined returns both parent and child nodes.
When you want to retrieve "child", please add to to the argument instead of from.
Related
I am trying to overwrite an object given specific changes to that object. The problem is that there are other nested objects that get overwritten as well. How would I prevent this?
const deviceChanges = {
"a": 5,
"card": {
"back": [
{
"key": "iphoneText",
"label": "IPHONE",
"value": "UPDATED VALUE FOR IPHONE"
},
]
}
};
let newBody = {
"a": 3,
"card": {
"back": [
{
"key": "androidText",
"label": "ANDROID",
"value": "androidOS"
},
{
"key": "samsungText",
"label": "SAMSUNG",
"value": "samsungOS"
},
{
"key": "iphoneText",
"label": "IPHONE",
"value": "iphone"
},
{
"key": "macbookText",
"label": "MACBOOK",
"value": "macbookOS"
}
]
},
"c": 8
};
const expected = {
"object": {
"a": 5,
"card": {
"back": [
{
"key": "androidText",
"label": "ANDROID",
"value": "androidOS"
},
{
"key": "samsungText",
"label": "SAMSUNG",
"value": "samsungOS"
},
{
"key": "iphoneText",
"label": "IPHONE",
"value": "UPDATED VALUE FOR IPHONE"
},
{
"key": "macbookText",
"label": "MACBOOK",
"value": "macbookOS"
}
]
},
"c": 8
}
};
Here is a Unit Test example of what I am trying to do. I want to take the changes object, and basically replace b.x in newBody, but I also want to preserve the other fields like b.y, a, and C. I want to make it as dynamic as possible, so if there is another object for newBody.b.x or another value for A, I want the code to be able to notice that and adequately change that. Does anyone have an idea on what to do here?
for (let [key, value] of Object.entries(changes)) {
for(let [key1, value1] of Object.entries(value)) {
newBody[key][key1] = value1;
}
}
This is what I have so far in terms of Code. But it only takes into account the fact that it only needs to traverse through two objects to replace. If I had something like:
const changes = {
"b": {
"x": "new",
"y": {
"n": "iphone"
}
}
};
The code would not work. How do I make it as dynamic as possible to realize how many objects it needs to replace?
Here's a function that assigns values for matching keys from a source object into a target. It does so recursively.
Lodash _merge() does something like this, probably handling many more edge cases than I've anticipated here (which is approximately none).
const changes = {
"b": {
"x": "changed",
"Z": "new key/value pair"
}
};
let newBody = {
"a": 3,
"b": {
"x": "old",
"y": "fields"
},
"c": 8
};
// overwrite values in target with matching keys in source
// side-effects target, also returns it
function merge(target, source) {
for (const [key, value] of Object.entries(source)) {
if (key in target) {
if (typeof value === 'object' && typeof target[key] === 'object') {
merge(target[key], value);
} else {
target[key] = value;
}
} else {
// the key in source isn't in the target. add it
target[key] = value;
}
}
return target;
}
const r = merge(newBody, changes)
console.log(r)
If you don't know how deep an object/array is nested you often need to make use of recursive functions (functions that call themselves)
The function I wrote below is that.
I'll try to explain how it works:
For simplicities sake we're gonna assume there is only 1 property per "layer" (the function can actually handle multiple - since we loop, but to explain the recursive part we're just gonna simplify the whole thing)
So you start by checking if the property of the changes object is itself an object or not. If it not an object, this means we are at the deepest level of this particular nesting, so we can replace the property of our actual object with the property of the changes object. If the property is however an object we need to go deeper. So what we do in this case is call the function again but pass only the property object (in our case we would pass {x: "new"} and {x: "old", y: "fields"} - we do this until we don't find an object as our property in which case we'll replace.
This way we can go however deep we need and replace only properties that are primitives.
const changes = {
"b": {
"x": "new",
}
};
let newBody = {
"a": 3,
"b": {
"x": "old",
"y": "fields"
},
"c": 8
};
function doTheThing(theObject, changes) {
const keys = Object.keys(changes)
keys.forEach(key => {
if (typeof changes[key] === "object" && changes[key] !== null) {
//this will also be true if changes[key] is an array
doTheThing(theObject[key], changes[key])
} else {
theObject[key] = changes[key]
}
})
return theObject
}
console.log(doTheThing(newBody, changes))
So we solved that problem with recursion (there are tons of resources about that if you need more information)
So I have an interesting problem which I have been able to solve, but my solution is not elegant in any way or form, so I was wondering what others could come up with :)
The issue is converting this response here
const response = {
"device": {
"name": "Foo",
"type": "Bar",
"telemetry": [
{
"timeStamp": "2022-06-01T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-02T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-03T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-04T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-05T00:00:00.000Z",
"temperature": 100,
"pressure": 50
}
]
}
};
Given this selection criteria
const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature']
and the goal is to return something like this
[
{"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-01T00:00:00.000Z", "device/telemetry/temperature": 100},
{"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-02T00:00:00.000Z", "device/telemetry/temperature": 100},
{"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-03T00:00:00.000Z", "device/telemetry/temperature": 100},
...,
{"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-05T00:00:00.000Z", "device/telemetry/temperature": 100},
]
If you are interested, here is my horrible brute force solution, not that familiar with typescript yet, so please forgive the horribleness :D
EDIT #1
So some clarifications might be needed. The response can be of completely different format, so we can't use our knowledge of how the response looks like now, the depth can also be much deeper.
What we can assume though is that even if there are multiple arrays in the reponse (like another telemetry array called superTelemetry) then the selection criteria will only choose from one of these arrays, never both :)
function createRecord(key: string, value: any){
return new Map<string, any>([[key, value]])
}
function getNestedData (data: any, fieldPath: string, records: Map<string, any[]>=new Map<string, any[]>()) {
let dataPoints: any = [];
const paths = fieldPath.split('/')
paths.forEach((key, idx, arr) => {
if(Array.isArray(data)){
data.forEach(
(row: any) => {
dataPoints.push(row[key])
}
)
} else {
data = data[key]
if(idx + 1== paths.length){
dataPoints.push(data);
}
}
})
records.set(fieldPath, dataPoints)
return records
}
function getNestedFields(data: any, fieldPaths: string[]){
let records: Map<string, any>[] = []
let dataset: Map<string, any[]> = new Map<string, any[]>()
let maxLength = 0;
// Fetch all the fields
fieldPaths.forEach((fieldPath) => {
dataset = getNestedData(data, fieldPath, dataset)
const thisLength = dataset.get(fieldPath)!.length;
maxLength = thisLength > maxLength ? thisLength : maxLength;
})
for(let i=0; i<maxLength; i++){
let record: Map<string, any> = new Map<string, any>()
for(let [key, value] of dataset){
const maxIdx = value.length - 1;
record.set(key, value[i > maxIdx ? maxIdx : i])
}
records.push(record)
}
// Normalize into records
return records
}
As per my understanding you are looking for a solution to construct the desired result as per the post. If Yes, you can achieve this by using Array.map() along with the Array.forEach() method.
Try this :
const response = {
"device": {
"name": "Foo",
"type": "Bar",
"telemetry": [
{
"timeStamp": "2022-06-01T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-02T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-03T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-04T00:00:00.000Z",
"temperature": 100,
"pressure": 50
},
{
"timeStamp": "2022-06-05T00:00:00.000Z",
"temperature": 100,
"pressure": 50
}
]
}
};
const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature'];
const res = response.device.telemetry.map(obj => {
const o = {};
fields.forEach(item => {
const splittedItem = item.split('/');
o[item] = (splittedItem.length === 2) ? response[splittedItem[0]][splittedItem[1]] : obj[splittedItem[2]];
});
return o;
})
console.log(res);
In what follows I will be concerned with just the implementation and runtime behavior, and not so much the types. I've given things very loose typings like any and string instead of the relevant generic object types. Here goes:
function getNestedFields(data: any, paths: string[]): any[] {
If data is an array, we want to perform getNestedFields() on each element of the array, and then concatenate the results together into one big array. So the first thing we do is check for that and make a recursive call:
if (Array.isArray(data)) return data.flatMap(v => getNestedFields(v, paths));
Now that we know data is not an array, we want to start gathering the pieces of the answer. If paths is, say, ['foo/bar', 'foo/baz/qux', 'x/y', 'x/z'], then we want to make recursive calls to getNestedFields(data.foo, ["bar", "baz/qux"]) and to getNestedFields(data.x, ["y", "z"]). In order to do this we have to split each path element at its first slash "/", and collect the results into a new object whose keys are the part to the left of the slash and whose values are arrays of parts to the right. In this example it would be {foo: ["bar", "baz/qux"], x: ["y", "z"]}.
Some important edge cases: for every element of paths with no slash, then we have a key with an empty value... that is, ["foo"] should result in a call like getNestedFields(data.foo, [""]). And if there is an element of paths that's just the empty string "", then we don't want to do a recursive call; the empty path is the base case and implies that we're asking about data itself. That is, instead of a recursive call, we can just return [{"": data}]. So we need to keep track of the empty path (hence the emptyPathInList variable below).
Here's how it looks:
const pathMappings: Record<string, string[]> = {};
let emptyPathInList = false;
paths.forEach(path => {
if (!path) {
emptyPathInList = true;
} else {
let slashIdx = path.indexOf("/");
if (slashIdx < 0) slashIdx = path.length;
const key = path.substring(0, slashIdx);
const restOfPath = path.substring(slashIdx + 1);
if (!(key in pathMappings)) pathMappings[key] = [];
pathMappings[key].push(restOfPath);
}
})
Now, for each key-value pair in pathMappings (with key key and with value restsOfPath) we need to call getNestedFields() recursively... the results will be an array of objects whose keys are relative to data[key], so we need to prepend key and a slash to their keys. Edge cases: if there's an empty path we shouldn't add a slash. And if data` is nullish then we will have a runtime error recursing down into it, so we might want to do something else there (although a runtime error might be fine since it's a weird input):
const subentries = Object.entries(pathMappings).map(([key, restsOfPath]) =>
(data == null) ? [{}] : // <-- don't recurse down into nullish data
getNestedFields(data[key], restsOfPath)
.map(nestedFields =>
Object.fromEntries(Object.entries(nestedFields)
.map(([path, value]) =>
[key + (path ? "/" : "") + path, value])))
)
Now subentries is an array of all the separate recursive call results, with the proper keys. We want to add one more entry correpsonding to data if emptyPathInList is true:
if (emptyPathInList) subentries.push([{ "": data }]);
And now we need to combine these sub-entries by taking their Cartesian product and spreading into a single object for each entry. By Cartesian product I mean that if subentries looks like [[a,b],[c,d,e],[f]] then I need to get [[a,c,f],[a,d,f],[a,e,f],[b,c,f],[b,d,f],[b,e,f]], and then for each of those we spread into single entries. Here's that:
return subentries.reduce((a, v) => v.flatMap(vi => a.map(ai => ({ ...ai, ...vi }))), [{}])
}
Okay, so let's test it out:
console.log(getNestedFields(response, fields));
/* [{
"device/name": "Foo",
"device/telemetry/timeStamp": "2022-06-01T00:00:00.000Z",
"device/telemetry/temperature": 100
}, {
"device/name": "Foo",
"device/telemetry/timeStamp": "2022-06-02T00:00:00.000Z",
"device/telemetry/temperature": 100
}, {
"device/name": "Foo",
"device/telemetry/timeStamp": "2022-06-03T00:00:00.000Z",
"device/telemetry/temperature": 100
}, {
"device/name": "Foo",
"device/telemetry/timeStamp": "2022-06-04T00:00:00.000Z",
"device/telemetry/temperature": 100
}, {
"device/name": "Foo",
"device/telemetry/timeStamp": "2022-06-05T00:00:00.000Z",
"device/telemetry/temperature": 100
}] */
That's what you wanted. Even though you said you will never walk into different arrays, this version should support that:
console.log(getNestedFields({
a: [{ b: 1 }, { b: 2 }],
c: [{ d: 3 }, { d: 4 }]
}, ["a/b", "c/d"]))
/* [
{ "a/b": 1, "c/d": 3 },
{ "a/b": 2, "c/d": 3 },
{ "a/b": 1, "c/d": 4 },
{ "a/b": 2, "c/d": 4 }
]*/
There are probably all kinds of crazy edge cases, so anyone using this should test thoroughly.
Playground link to code
I am trying to access JSON values. This is the JSON object:
{
"attrs": {
"width": 1728,
"height": 787,
"dragabble": true
},
"className": "Stage",
"children": [
{
"attrs": {},
"className": "Layer",
"children": [
{
"attrs": {
"stroke": "green",
"strokeWidth": "5",
"points": [
348,564.125
]
},
"className": "Line"
}
]
}
]
}
And I am trying to use these values, like points, here:
socket.on("canvas-data", function(data){
var interval = setInterval(function(){
if(isDrawing) return;
setIsDrawing(true);
clearInterval(interval);
var obj = JSON.parse(data);
setStageData(obj);
var layer = new Konva.Layer();
var lines = new Konva.Line(
{
stroke: stageData.stroke,
strokeWidth: stageData.strokeWidth,
points: stageData.points
})
layer.add(lines);
stageEl.current.add(layer);
}, 200)
})
data is the JSON string, I tried to parse data into obj, set my stageData to obj and then set the corresponding JSON attributes to the values like stroke, strokeWidth and points. This doesn't work however, they're undefined. How do I access them?
(I also tried skipping the step where I set my stageData to obj, and just use obj.stroke instead of stageData.stroke etc.)
You can just skip using setStageData() and use the parsed object directly if you wish, or just name the parsed object stageData by default.
In any case, when you have nested objects and values in an Object, you access them by using the correct index, in your case, it would look this way:
socket.on("canvas-data", function(data) {
var interval = setInterval(function() {
if (isDrawing) return;
setIsDrawing(true);
clearInterval(interval);
var stageData = JSON.parse(data);
var layer = new Konva.Layer();
var lines = new Konva.Line(
{
stroke: stageData.children[0].children[0].attrs.stroke,
strokeWidth: stageData.children[0].children[0].attrs.strokeWidth,
points: stageData.children[0].children[0].attrs.points
});
layer.add(lines);
stageEl.current.add(layer);
}, 200);
})
Doesn't look very nice, but it works. You can always use this app called JSON Path list, which shows you all the possible paths and their values in a JSON object.
So this is a problem that I have no idea where to even start so even just a pointer in the right direction would be great.
So I have data that looks like so:
data = {
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}
]
},
{
...
},
{
...
},
...
]
}
}
Now from the example, within 'agg' there are N 'agg1' results, within each 'agg1' result there is a 'keyWeWant'. Each 'agg1' result also has a list of 'some_nested_agg' results which each contain a 'keyWeWant2'. Each 'keyWeWant2' value is associated a single 'keyWeWant' value somewhere up in the hierarchy. Similarly each 'keyWeWant2' also contains a set of results for 'some_nested_agg2' (not a list but rather a map this time). Each of the set of results contains a 'keyWeWant3'.
Now I want to flatten this structure while still preserving the association between 'keyWeWant', 'keyWeWant2', and 'keyWeWant3' (I'm essentially de-normalizing) to get something like so:
What I want the function to look like:
[
{
"keyWeWant" : "*-20",
"keyWeWant2" : 20,
"keyWeWant3" : 2.857142857142857
},
{
"keyWeWant" : "*-20",
"keyWeWant2" : 25,
"keyWeWant3" : 6.375
},
{
...
},
{
...
}
]
This is an example where there is only depth 3 but there could be arbitrary depth with some nested values being lists and some being arrays/list.
What I would like to do is write a function to take in the keys I want and where to find them, and then go get the keys and denormalize.
Something that looks like:
function_name(data_map, {
"keyWeWant" : ['agg', 'agg1'],
"keyWeWant2" : ['agg', 'agg1', 'some_nested_agg'],
"keyWeWant" : ['agg', 'agg1', 'some_nested_agg', 'some_nested_agg2']
})
Any ideas? I'm familiar with Java, Clojure, Java-script, and Python and am just looking for a way to solve this that's relatively simple.
Here is a JavaScript (ES6) function you could use:
function flatten(data, keys) {
var key = keys[0];
if (key in data)
keys = keys.slice(1);
var res = keys.length && Object.keys(data)
.map( key => data[key] )
.filter( val => Object(val) === val )
.reduce( (res, val) => res.concat(flatten(val, keys)), []);
return !(key in data) ? res
: (res || [{}]).map ( obj => Object.assign(obj, { [key]: data[key] }) );
}
// Sample data
var data = {
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}
]
},
]
}
};
// Flatten it by array of keys
var res = flatten(data, ['keyWeWant', 'keyWeWant2', 'keyWeWant3']);
// Output result
console.log(res);
Alternative using paths
As noted in comments, the above code does not use path information; it just looks in all arrays. This could be an issue if the keys being looked for also occur in paths that should be ignored.
The following alternative will use path information, which should be passed as an array of sub-arrays, where each sub-array first lists the path keys, and as last element the value key to be retained:
function flatten(data, [path, ...paths]) {
return path && (
Array.isArray(data)
? data.reduce( (res, item) => res.concat(flatten(item, arguments[1])), [] )
: path[0] in data && (
path.length > 1
? flatten(data[path[0]], [path.slice(1), ...paths])
: (flatten(data, paths) || [{}]).map (
item => Object.assign(item, { [path[0]]: data[path[0]] })
)
)
);
}
// Sample data
var data = {
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}
]
},
]
}
};
// Flatten it by array of keys
var res = flatten(data, [
['agg', 'agg1', 'keyWeWant'],
['some_nested_agg', 'keyWeWant2'],
['some_nested_agg2', 'keyWeWant3']]);
// Output result
console.log(res);
There is probably a better way to solve this particular problem (using some ElasticSearch library or something), but here's a solution in Clojure using your requested input and output data formats.
I placed this test data in a file called data.json:
{
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}]
}]}
}
Then Cheshire JSON library parses the data to a Clojure data structure:
(use '[cheshire.core :as cheshire])
(def my-data (-> "data.json" slurp cheshire/parse-string))
Next the paths to get are defined as follows:
(def my-data-map
{"keyWeWant" ["agg", "agg1"],
"keyWeWant2" ["agg", "agg1", "some_nested_agg"],
"keyWeWant3" ["agg", "agg1", "some_nested_agg", "some_nested_agg2"]})
It is your data_map above without ":", single quotes changed to double quotes and the last "keyWeWant" changed to "keyWeWant3".
find-nested below has the semantics of Clojure's get-in, only then it works on maps with vectors, and returns all values instead of one.
When find-nested is given a search vector it finds all values in a nested map where some values can consist of a vector with a list of maps. Every map in the vector is checked.
(defn find-nested
"Finds all values in a coll consisting of maps and vectors.
All values are returned in a tree structure:
i.e, in your problem it returns (20 25) if you call it with
(find-nested ['agg', 'agg1', 'some_nested_agg', 'keyWeWant2']
my-data).
Returns nil if not found."
[ks c]
(let [k (first ks)]
(cond (nil? k) c
(map? c) (find-nested (rest ks) (get c k))
(vector? c) (if-let [e (-> c first (get k))]
(if (string? e) e ; do not map over chars in str
(map (partial find-nested (rest ks)) e))
(find-nested ks (into [] (rest c)))) ; create vec again
:else nil)))
find-nested finds the values for a search path:
(find-nested ["agg", "agg1", "some_nested_agg", "keyWeWant2"] my-data)
; => (20 25)
If all the paths towards the "keyWeWant's are mapped over my-data these are the slices of a tree:
(*-20.0
(20 25)
(2.857142857142857 6.375))
The structure you ask for (all end results with paths getting there) can be obtained from this tree in function-name like this:
(defn function-name
"Transforms data d by finding (nested keys) via data-map m in d and
flattening the structure."
[d m]
(let [tree (map #(find-nested (conj (second %) (first %)) d) m)
leaves (last tree)
leaf-indices (range (count leaves))
results (for [index leaf-indices]
(map (fn [slice]
(if (string? slice)
slice
(loop [node (nth slice index)]
(if node
node
(recur (nth slice (dec index)))))))
tree))
results-with-paths (mapv #(zipmap (keys m) %) results)
json (cheshire/encode results-with-paths)]
json))
results uses a loop to step back if a leaf-index is larger than that particular slice. I think it will work out for deeper nested structures as well -if a next slice is always double the size of a previous slice or the same size it should work out -, but I have not tested it.
Calling (function-name my-data my-data-map) leads to a JSON string in your requested format:
[{
"keyWeWant": "-20.0",
"keyWeWant2": 20,
"keyWeWant3": 2.857142857142857 }
{
"keyWeWant": "-20.0",
"keyWeWant2" 25,
"keyWeWant3" 6.375 }]
/edit
I see you were looking for a relatively simple solution, that this is not. :-) maybe there is one without having it available in a library. I would be glad to find out how it can be simplified.
Here is my Javascript code:
var subRow = [];
var rowarr = [];
subRow.push({ v: "Jay" });
subRow.push({ v: "Ram" });
rowarr.push({ c: subRow });
subRow.length = 0;
subRow.push({ v: "Jay1" });
subRow.push({ v: "Ram1" });
rowarr.push({ c: subRow });
console.log(JSON.stringify(rowarr));
The output is:
[{
"c": [{
"v": "Jay1"
}, {
"v": "Ram1"
}]
}, {
"c": [{
"v": "Jay1"
}, {
"v": "Ram1"
}]
}]
The expected output is:
[{
"c": [{
"v": "Jay"
}, {
"v": "Ram"
}]
}, {
"c": [{
"v": "Jay1"
}, {
"v": "Ram1"
}]
}]
Can anyone explain why it so?
Arrays are handled by reference.
subRow.length = 0; erases the contents of the array.
rowarr then contains two pointers to the same array (which only has the content in it that you put there after emptying it)
Change subRow.length = 0; to subRow = [] to work on a new array instead of modifying the existing one.
subRow points to an object. When you push it onto rowArr you create a reference to that object. You push it twice, that's two references to one object. When you edit subRow both references to the object see the changes, so you've trampled all over the old contents of the object - they are not stored anywhere else, so they are completely lost. You need to create a brand new object instead of editing the old object.