Accessing json using a string - javascript

I have some json : data.
I have a string build that looks like this:
sections.1.subsections.0.items.0.citation.paragraph
What I need to do is manipulate that string to be able to access that value in data. So turn it into something usable:
data['sections'][1]['subsections'][0]['items'][0]['citation']['paragraph']
And then use it to change that value in data. So:
data['sections'][1]['subsections'][0]['items'][0]['citation']['paragraph'] = 'new value'
I can split the original string on the . and I think that gets me somewhere but I'm not at all sure how to then re-use the parts to allow me access to that value in data.
Thanks!

I'm still not quite sure why you're handling the JSON in this fashion but if it has to be done this way, then you'll need to use recursion to access the data. Assuming I mapped your object correctly, the example below should provide you with a method for doing this:
var data = {
sections: [
{
subsections: []
},
{
subsections: [
{
items: [
{
citation: {
paragraph: "Citation by Warlock"
}
}
]
}
]
}
]
};
var string = "sections.1.subsections.0.items.0.citation.paragraph",
parts = string.split('.');
function getValue(tree, index) {
if(index < (parts.length - 1)) {
return getValue(tree[parts[index]], index + 1);
} else {
return tree[parts[index]];
}
}
function setValue(tree, index, newValue) {
if(index < (parts.length - 1)) {
setValue(tree[parts[index]], index + 1, newValue);
} else {
tree[parts[index]] = newValue;
}
}
alert(getValue(data, 0));
setValue(data, 0, "New Citation By Warlock");
alert(getValue(data, 0));
The idea is that the getValue(...); function steps one layer deep into your JSON and then recursively calls itself. This allows the data to be accessed one step at a time until the last part is retrieved. The value is then returned via the recursion in all the previous function calls.
The same idea is true for setting the value. The setValue(...); function steps into the JSON one layer at a time passing the new value to set until it's reached the last nested layer. The value is then set for the specified property.
EDIT:
A better implementation would be to pass the parts array into the getValue(...); and setValue(...); function to eliminate external dependencies. Then, within the function shift the array's data values to step through the nested layers. This eliminates the need for index tracking based on the original array's values:
var data = {
sections: [
{
subsections: []
},
{
subsections: [
{
items: [
{
citation: {
paragraph: "Citation by Warlock"
}
}
]
}
]
}
]
};
var string = "sections.1.subsections.0.items.0.citation.paragraph",
parts = string.split('.');
function getValue(temp, tree) {
if(temp.length > 1) {
tree = tree[temp[0]];
temp.shift();
return getValue(temp, tree);
} else {
return tree[temp[0]];
}
}
function setValue(temp, tree, newValue) {
if(temp.length > 1) {
tree = tree[temp[0]];
temp.shift();
setValue(temp, tree, newValue);
} else {
tree[temp[0]] = newValue;
}
}
alert(getValue(parts, data));
setValue(parts, data, "New Citation By Warlock");
alert(getValue(parts, data));

Related

JSON Group by Name and then Display Latest Record

Using only JavaScript, I need to
Group by code.
Get latest modifieddate.
Display total grouped code as Count.
Starting JSON Result
[
{"ID":1,"code":"AAA","modifieddate":"2019-06-01","user":"John"},
{"ID":2,"code":"AAA","modifieddate":"2019-06-02","user":"Jane"},
{"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue"},
{"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick"},
{"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe"}
]
Desired JSON Result set
[
{"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue","Count":"3"},
{"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick","Count":"1"},
{"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe","Count":"1"}
]
Tried using reduce method.
I do not have access to modify the server side API code.
I am using Aurelia JS.
You can use Array.reduce to group the result set by each item's code property, incrementing Count as needed, then take the values from the accumulation object. Along the way, we perform a date comparison to determine which the most recent entry to include in the result.
const data = [ {"ID":1,"code":"AAA","modifieddate":"2019-06-01","user":"John"}, {"ID":2,"code":"AAA","modifieddate":"2019-06-02","user":"Jane"}, {"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue"}, {"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick"}, {"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe"} ];
const result = Object.values(data.reduce((a, e) => {
if (!a[e.code]) {
a[e.code] = {...e, Count: 0};
}
if (Date.parse(e.modifieddate) > Date.parse(a[e.code].modifieddate)) {
a[e.code] = {...e, Count: a[e.code].Count};
}
a[e.code].Count++;
return a;
}, {}));
console.log(result);
By the way, this is just a plain JS array we're working with, not JSON.
This should get you:
let array = [
{"ID":1,"code":"AAA","modifieddate":"2019-06-01","user":"John"},
{"ID":2,"code":"AAA","modifieddate":"2019-06-02","user":"Jane"},
{"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue"},
{"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick"},
{"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe"}
]
let result = array.reduce(function(total, currentValue, currentIndex, arr) {
let index = total.findIndex(function(entry) { return entry.code == currentValue.code; })
if (index >= 0) { // entry already exists
// check modified
if (total[index].modifieddate > currentValue.modifieddate) { // already have most recent of the two
total[index].Count += 1;
} else { // need to replace with more recent
currentValue.Count = total[index].Count + 1;
total[index] = currentValue;
}
} else { // first record for this code
currentValue.Count = 1;
total.push(currentValue);
}
return total;
}, []);
console.log(result);
Here is a working js-fiddle
Note: Comments are made in code block

How to save distinct values in an object in javascript?

I have a bunch of log data which is stored in a variable. Each log value contains a camera name and system ip. I want to create an object which has names as all the distinct system ip's and corresponding value as an array which contains all the camera names corresponding to that system ip. Below is my code ---
$http(req).success(function(data){
$scope.logs = data;
$scope.cameras={};
var v =$scope.logs[0].systemIp;
$scope.cameras["v"]=[];
$scope.cameras["v"].push($scope.logs[0].cameraName);
for(i=1;i<$scope.logs.length;i++){
v=$scope.logs[i].systemIp;
var flag=0;
for(j in $scope.cameras){
if(j==="v")
{
flag=1;
break;
}
}
if(flag==0)
{
$scope.cameras["j"]=[];
$scope.cameras["j"].push($scope.logs[i].cameraName);
}
else if(flag==1)
{
$scope.cameras["v"].push($scope.logs[i].cameraName);
}
}});
And this is what my data looks like --
[{
"_id": "57683fd82c77bb5a1a49a2aa",
"cameraIp": "192.16.0.9",
"cameraName": "garage2",
"systemIp": "192.168.0.2"
},
{
"_id": "57683f8e2c77bb5a1a49a2a9",
"cameraIp": "192.16.0.8",
"cameraName": "garage1",
"systemIp": "192.168.0.2"
},
{
"_id": "57683f5e2c77bb5a1a49a2a8",
"cameraIp": "192.16.0.7",
"cameraName": "Back Door",
"systemIp": "192.168.0.4"
}]
When I print $scope.cameras on my console it gives this as the output -
Object { v: Array[3] }
I want by cameras object to look like this --
{ "192.168.0.2" : [ "garage1" , "garage2"] ,
"192.168.0.4" : [ "Back Door"] }
I am new to javascript, any help is appreciated.
If you are using the Lodash or Underscore library (which I highly recommend), you can just use the _.groupBy() function to do what you are after (along with some other functions to ensure all values are unique).
However, you can also easily implement it yourself:
function groupByDistinct(arr, prop, mapFn) {
mapFn = mapFn || function (x) { return x; };
var output = {};
arr.forEach(function (item) {
var key = item[prop],
val = mapFn(item);
if (!output[key]) {
output[key] = [val];
return;
}
if (output[key].indexOf(val) < 0) {
output[key].push(val);
}
});
return output;
}
Use it for your code like so:
$scope.cameras = groupByDistinct(data, 'cameraIp', function (logEntry) {
return logEntry.cameraName;
});
You are passing a string such as "v" or "j" as your object key, and this string are actually ending being your object key and not the value of this variables as you want. You can use something like this:
for(i=0; i < $scope.logs.length; i++){
var _sysIp = $scope.logs[i].systemIp,
_camName = $scope.logs[i].cameraName;
if(!$scope.cameras.hasOwnProperty(_sysIp)) {
$scope.cameras[_sysIp] = [_camName];
} else if ($scope.cameras[_sysIp].indexOf(_camName) < 0) {
$scope.cameras[_sysIp].push(_camName);
}
}

AngularJS: Merge object by ID, i.e. replace old entry when IDs are identical

I am using Ionic with AngularJS and I am using a localForage database and AJAX via $http. My app has a news stream that contains data like this:
{
"feed":[
{
"id":"3",
"title":"Ein Hund",
"comments:"1"
},
{
"id":"2",
"title":"Eine Katze",
"comments":"2"
}
],
"ts":"20150907171943"
}
ts stands for Timestamp. My app saves the feed locally via localForage.
When the app starts it first loads the locally saved items:
$localForage.getItem("feed").then(function(val) { vm.feed = val; })
Then, it loads the new or updated items (ts < current timestamp) and merges both the old and new data:
angular.extend(vm.feed, response.data.feed);
Updated items look like this:
{
"feed":[
{
"id":"2",
"title":"Eine Katze",
"comments":"4"
}
],
"ts":"20150907171944"
}
That is, the comments count on feed item 2 has changed from 2 to 4. When I merge the old and new data, vm.feed has two items with id = 2.
Does angularjs has a built-in "merge by id" function, i. e. copy from source to destination (if it is a new element), or otherwise replace the old element? In case angularjs does not have such a function, what's the best way to implement this?
Thanks in advance!
angular.merge(vm.feed, response.data.feed);
// EDIT
Probably, it will not merge correctly, so you have to update all properties manually. Update ts property and then find your object with id and replace it.
There is no builtin, I usually write my own merge function:
(function(){
function itemsToArray(items) {
var result = [];
if (items) {
// items can be a Map, so don't use angular.forEach here
items.forEach(function(item) {
result.push(item);
});
}
return result;
}
function idOf(obj) {
return obj.id;
}
function defaultMerge(newItem, oldItem) {
return angular.merge(oldItem, newItem);
}
function mergeById(oldItems, newItems, idSelector, mergeItem) {
if (mergeItem === undefined) mergeItem = defaultMerge;
if (idSelector === undefined) idSelector = idOf;
// Map retains insertion order
var mapping = new Map();
angular.forEach(oldItems, function(oldItem) {
var key = idSelector(oldItem);
mapping.set(key, oldItem);
});
angular.forEach(newItems, function(newItem) {
var key = idSelector(newItem);
if (mapping.has(key)) {
var oldItem = mapping.get(key);
mapping.set(key, mergeItem(newItem, oldItem));
} else {
// new items are simply added, will be at
// the end of the result list, in order
mapping.set(key, newItem);
}
});
return itemsToArray(mapping);
}
var olds = [
{ id: 1, name: 'old1' },
{ id: 2, name: 'old2' }
];
var news = [
{ id: 3, name: 'new3' },
{ id: 2, name: 'new2' }
];
var merged = mergeById(olds, news);
console.log(merged);
/* Prints
[
{ id: 1, name: 'old1' },
{ id: 2, name: 'new2' },
{ id: 3, name: 'new3' }
];
*/
})();
This builds a Map from the old items by id, merges in the new items, and converts the map back to list. Fortunately the Map object will iterate on the entries in insertion order, according to the specification. You can provide your idSelector and mergeItem functions.
Thanks hege_hegedus. Based on your code, I've written my own and tried to use less loops to speed things up a bit:
function updateCollection(localCollection, fetchedCollection) {
angular.forEach(fetchedCollection, function(item) {
var append = true;
for (var i = 0; i < localCollection.length; i++) {
if (localCollection[i].id == item.id) {
// Replace item
localCollection[i] = item;
append = false;
break;
} else if (localCollection[i].id > item.id) {
// Add new element at the right position, if IDs are descending check for "< item.id" instead
localCollection.splice(i, 0, item);
append = false;
break;
}
}
if (append) {
// Add new element with a higher ID at the end
localCollection.push(item);
// When IDs are descending use .unshift(item) instead
}
});
}
There is still room for improvements, i. e. the iteration through all the objects should use binary search since all items are sorted by id.

refactor two $.each() loops to one

I have a JSON object like this...
{
"tasks":[
{
"id":"task_3",
"taskName":"Task A",
"assignee":"Barrack Obama",
"timeReqOptimisitic":"4",
"timeReqNormal":"8",
"timeReqPessimistic":"14",
"timeUnit":"Days",
"timeReq":"8.33",
"positionX":493,
"positionY":101,
"lockStatus":"unlocked"
}
],
"milestones":[
{
"id":"task_1",
"milestoneName":"Start",
"positionX":149,
"positionY":109,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-07"
},
{
"id":"task_2",
"milestoneName":"Finish",
"positionX":989,
"positionY":367,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-22"
}
],
"connections":[
{
"connectionId":"con_10",
"pageSourceId":"task_1",
"pageTargetId":"task_3"
},
{
"connectionId":"con_20",
"pageSourceId":"task_3",
"pageTargetId":"task_2"
}
]
}
...this is a minimal version. In practice, there are numerous items in "tasks", "milestones" and "connections".
I need to iterate through the object and determine the "id" of the "milestones" item with the lowest/earliest "milestoneDate", then identify the "connections" item that has the same value for its "pageSourceId" and return its "pageTargetId".
So in the above example:
Step 1) Iterate through the object and determine the "id" of the "milestones" item with the lowest/earliest "milestoneDate".
Answer: milestones.id = "task_1"
Step 2) Identify the "connections" item that has the same value for its "pageSourceId".
Answer: connections.pageSourceId = "task_1"
Step 3) Return its "pageTargetId".
Answer: "task_3"
I have a working example here. However, I would like to know if there is a way to accomplish this without using the extremely high start date and also in one loop.
As you are not parsing the same array on these two loops, there is no way to merge your loops.
Anyway, you can yet remove the loops to access to the arrays:
http://jsfiddle.net/gael/sruvtwre/2/
$.each(object.milestones, function( index, value ) {
if(startDate > parseDate(value.milestoneDate)) {
startDate = parseDate(value.milestoneDate);
id = value.id
}
});
$.each(object.connections, function( index, value ) {
if(id == value.pageSourceId) {
pageTargetId = value.pageTargetId;
}
});
May be also sorting, and indexing your datas. Then you would need no loops:
Elements in milestones should be sorted, so the earliest milestones element would be milestones[0].
Elements in connections should be indexed by their pageTargetId property, so the requested element should be connections[id].
Your two loops would become:
var pageTargetId= object.connections[ object.milestones[0].id ].pageTargetId;
http://jsfiddle.net/gael/sruvtwre/4/
As said in comments, sorting is not an optimal solution, even if that does not really matter for small sets.
Roughly, there is no no needs to sort all the datas, just the latest matters.
You can use array reduce method, as an comparable alternative to a simple loop:
var latestMilestone= object.milestones.reduce(function(milestone1, milestone2){
if( parseDate(milestone1.milestoneDate) > parseDate(milestone2.milestoneDate) )
return milestone1;
else
return milestone2;
//convert date to timestamp
function parseDate(date) {
var parts = date.split('-');
return Date.UTC(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based
}
});
How about this:
Assuming you get the milestones.id = "task_1" in first loop; outside the loop we can have use jQuery grep. As connections will have unique pageSourceId, grep will return an array with only one object.
var filteredData = jQuery.grep('CONNECTIONS_ARRAY', function(element, index){
return element.pageSourceId == 'MILESTONES_ID'; // Which you get in the loop earlier
});
Then we can access pageTargetId like this:
if(filteredData.length){
filteredData[0].pageTargetId;
}
Try
var dates = []
, ids = []
, filtered = $.map(data.milestones, function(value, index) {
dates.push(new Date(value.milestoneDate).getTime());
ids.push(value.id);
if (dates.length === data.milestones.length) {
var id = ids[$.inArray(Math.min.apply(Math, dates), dates)]
, res = $.grep(data.connections, function(task, key) {
return task.pageSourceId === id
})[0].pageTargetId;
return res
}
})[0]; // `"task_3"`
var data = {
"tasks":[
{
"id":"task_3",
"taskName":"Task A",
"assignee":"Barrack Obama",
"timeReqOptimisitic":"4",
"timeReqNormal":"8",
"timeReqPessimistic":"14",
"timeUnit":"Days",
"timeReq":"8.33",
"positionX":493,
"positionY":101,
"lockStatus":"unlocked"
}
],
"milestones":[
{
"id":"task_1",
"milestoneName":"Start",
"positionX":149,
"positionY":109,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-07"
},
{
"id":"task_2",
"milestoneName":"Finish",
"positionX":989,
"positionY":367,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-22"
}
],
"connections":[
{
"connectionId":"con_10",
"pageSourceId":"task_1",
"pageTargetId":"task_3"
},
{
"connectionId":"con_20",
"pageSourceId":"task_3",
"pageTargetId":"task_2"
}
]
};
var dates = []
, ids = []
, filtered = $.map(data.milestones, function(value, index) {
dates.push(new Date(value.milestoneDate).getTime());
ids.push(value.id);
if (dates.length === data.milestones.length) {
var id = ids[$.inArray(Math.min.apply(Math, dates), dates)]
, res = $.grep(data.connections, function(task, key) {
return task.pageSourceId === id
})[0].pageTargetId;
return res
}
})[0];
document.write(filtered);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>

Look for a value for a given key in JSON and change the value using javascript

I am looking to write a function which can look up a value based on a key and replace that value with another. The key is a tree from the start node of JSON. Here is the example.
var myData = {
name : 'Dan',
address: {
city : 'Santa Clara',
details : {
'prevhouse' : ''
}
}
}
Input to the function is a key tree. For eg, myData-address-details-prevhouse
When I pass this key with a new value, say 'Texas', the prevhouse value will get changed to the new value I am sending.
and new JSON will be
var myData = {
name : 'Dan',
address: {
city : 'Santa Clara',
details : {
'prevhouse' : 'Texas'
}
}
}
Here is what I wrote so far
var tree = key.split("-");
now the tree variable contains ["myData","address", "details","prevhouse"]
I know that we can access the object using myData[tree[0]][tree[1]][tree[2]], but somehow not able to get it dynamic from parsed value.
how do we generate this dynamically since the length of the depth is not known till runtime.
Hope to get a help.
try with this code:
var myData = {
name: 'Dan',
address: {
city: 'Santa Clara',
details: {
prevhouse: ''
}
}
};
function setAttribute(obj, key, value) {
var i = 1,
attrs = key.split('-'),
max = attrs.length - 1;
for (; i < max; i++) {
attr = attrs[i];
obj = obj[attr];
}
obj[attrs[max]] = value;
console.log('myData=', myData);
}
setAttribute(myData, "myData-address-details-prevhouse", "Texas");
here a working jsfiddle demo; see the console for the result
You should be able to iterate through each key because your JSON is just a JS object. So go through each key, check if it's defined, if it is, use that object for your next check. That'll get you where you want to go. Keep in mind you'll be setting the last key to your value.
basic psuedo-code without dealing with setting:
obj = data;
for (key in keys) {
obj = obj[key]
}
Something like this would do:
function update(node, path, value) {
path = path.split('-');
do {
node = node[path.splice(0, 1)];
} while(path.length > 1);
node[path[0]] = value;
}
Given that myData is the object, I think you should be using myData[tree[1]][tree[2]][tree[3]] and throwing away the first item in the array.
Something like this should work recursively (untested)
function updateValue(obj, key, value)
{
var keys = key.split('-');
updateObjectValue(obj, keys.shift(), value);
}
function updateObjectValue(obj, keyArray, value)
{
if (keyArray.length == 1) {
obj[keyArray[0]] = value;
}
else if (keyArray.length > 1) {
updateObject(obj[keyArray[0]], keyArray.shift(), value);
}
}

Categories