Parsing a JSON object in javascript (key/value pairs) - javascript

I'm trying to get my head around NodeJS by writing a few test apps. I've managed okay so far but the one thing that's tripping me up is the following.
I've got it reading a directory for folders, and then parsing the settings.json files within each folder (so I can build a UI on the fly based on the folder contents). The problem is, I can't seem to "step down a level" with the JSON object.
For example, coming from a background in php where you could do something like the following to 'step' through the array:
<?php
$arr = [
'folder1' =>
[ 'name' => 'test',
'icon' => 'icon-test'
],
'folder2' =>
[ 'name' => 'test-2',
'icon' => 'icon-test-2'
]
];
foreach($arr as $k => $v){
// folder1 level
foreach($v as $k2 => $v2){
// name / icon level.
echo $k2 . ' is ' . $v2;
}
}
?>
Is there an equivalent in JS? I can do the first "level" by doing this:
function getDirectories (srcpath) {
return fs.readdirSync(srcpath)
.filter(file => fs.lstatSync(path.join(srcpath, file)).isDirectory())
}
var d = getDirectories('modules');
var modules = {};
// the following reads the json in each dir into an object like:
// { 'dirname': { 'key1': 'val1', 'key2': 'val2' }, 'dirname2'... }
for(var p in d){
modules[d[p]] = JSON.parse(fs.readFileSync('./modules/'+d[p]+'/config.json', 'utf8'));
}
for(var k in modules){
console.log(k);
}
But ideally I need to extract "name" and "icon" from the JSON. I can't seem to find any way of doing this.
I understand this is probably messy but I'm just getting my head around NodeJS. For full clarity, directory structure and my simple JSON files below:
modules directory structure
modules
|____ test
|____ config.json
|____ test-2
|____ config.json
config.json (example)
{
"name": "test",
"icon": "test-icon"
}

for(var module of modules){
console.log(module.name,module.icon);
//or over all
for(var value of module) console.log(value);
}
The for...of loops over values. With for..in (looping over keys) :
for(var name in modules){
var module=modules[name];
for(key in module){
console.log(name+"/"+key+":"+module[key]);
}
}

You're currently using a for in loop, and while that works, when iterating over arrays you ideally want to use for next loops.
Here is an example:
for (var i = 0; i < folders.length; i++) {
var folder = folders[i]
/* work with directory .. */
for (var j = 0; j < folder.items.length; j++){
var item = folder.items[j]
/* work with item.. */
}
}
The reason for not using for in when iterating over array is because an array can contain objects that aren't actually part of the array, i.e a function.
Using a for in loop, you will iterate over the array contains, and then any object/function attached to that array.
for next loops will only iterate over the array contents.

I don't know do you have only 1 subdirectory or can that be multiple? If it can be any depth use a recursive function like:
function iterateFolder(array) {
for (var i = 0; i < array.length; i++) {
if ($.isArray(array[i]))
iterateFolder(array[i]);
else
//Add to global variable for example or parse just the values
}
}

try doing a for of loop and not a for in loop
// example from a old proj
// your code has in
// if you are using es6 syntax, you should use let instead of var
for (let element of section.elements) {
let $element;
$element = $parent.find(`label[for=${element._data_text}]`);
$element.html(`<span>${element[lang]}</span>`);
}
here is a working example with my json
import lang_data from "../data/languages.json";
$(function () {
console.log("onload hit");
$("[data-language]").on("click", function () {
let $this = $(this);
let data = $this.data();
let lang = data.language;
$("[data-language]").removeClass("active");
$this.addClass("active");
switchLanguage(lang);
return true;
});
})
function switchLanguage(lang) {
for (let section of lang_data.sections) {
// the form is loaded by JS. I could not add a data-text to it.
if (section.id === "form1_handle_after") {
let $parent = $("#form1");
for (let element of section.elements) {
let $element;
if (element._data_text == "c-submit-button") {
$element = $parent.find(`#${element._data_text}`);
} else {
$element = $parent.find(`label[for=${element._data_text}]`);
}
$element.html(`<span>${element[lang]}</span>`);
}
} else {
let $parent = $(`#${section.id}`);
for (let element of section.elements) {
let $element = $parent.find(`[data-text=${element._data_text}]`);
// text needs to be highlighted
if(element.mark !== null){
let string = element[lang];
let find = element.mark[lang];
let new_string = string.replace(find, `<mark>${find}</mark>`)
// I assume it is wrapped in quotes
$element.html(`<span>“${new_string}”</span>`);
}else{
$element.html(`<span>${element[lang]}</span>`);
}
}
}
}
}
Here is a sample of my data
{
"sections": [
{
"id": "navbar_main",
"elements": [
{
"_data_text": "question",
"eng": "Have a Question?",
"chi": "有个问题?",
"mark": null
}
]
}
]
}
You can view the html and markup at http://professional.ongeo.msu.edu/

I know you are starting with node, but i guess you should use "reduce" instead of those "for loops" for this case (it's just my personal opinion xd). You can find many guides of how to use this function, you variable "d" is an array and all arrays has the "reduce" function.
Something like this:
const modules = d.reduce((all, current) => {
all[current] = current || {}
all[current] = JSON.parse(fs.readFileSync(__dirname + '/modules/'+ current +'/config.json', 'utf8'))
return all;
}, {})
I test it creating some folders and gave me this:
​​​​​{ test: { name: 'test', icon: 'test-icon' },​​​​​ ​​​tes_2: { name: 'test-2', icon: 'test-icon-2' } }​​​​​

Related

Remove all duplicate object elements from array

This one's been giving me problems for a week, cross my fingers one of you can help me here...
This application was built on Laravel and the front scaffolded using Vue.
Thing is I have an array of objects that is supposed to be sent to the backend in order for it to be stored in a database. Thing is this is an editor and the idea is not reload the page every time something is changed, so here comes my problem...
The way of getting the information is through window.postMessage(), so it seems the information lingers on even after saving, since the page behavior is for it to not reload, I have tried emptying the array after firing the save function. Now it works the first time because the array is empty so there's nothing to compare it to, it also works the second time, but from the third time on, it duplicates some of the objects inside and stores them in DB.
Here's my code:
saveNewSettings() {
//THIS IS THE ARRAY I NEED TO EMPTY (ALREADY DECLARED IN THE DATA OBJECT)
/* this.newItems = [
{ id="123", name="a", otherProps="someProps" },
{ id="456", name="ab, otherProps="someProps" },
{ id="789", name="c", otherProps="someProps" },
]
*/
//THIS IS THE AN EMPTY ARRAY I'M USING TO COMPARE LATER ON... (ALREADY DECLARED IN THE DATA OBJECT)
// this.newlyCreatedItems = [];
if ( !this.newlyCreatedItems.length ) {
this.newlyCreatedItems = this.newItems;
} else {
for ( let i = 0; i < this.newItems.length; i++ ) {
for ( let j = 0; j < this.newlyCreatedItems.length; j++ ) {
if ( this.newItems[i].id == this.newlyCreatedItems[j].id ) {
this.newItems.splice( i, 1 );
}
}
}
}
//THIS IS THE SERVICE I USE TO SEND THE INFO TO THE BACK END
//THIS ONE HAS BEEN IMPORTED FROM AN SERVICE FILE
settingsService.save( this.newItems )
.then(response => {
//WHAT TO DO AFTER THE RESPONSE GOES HERE
});
}
So here's the thing, firing the function for the first time, since it's the first, doesn't duplicate anything in the database... For the second time, it works well and it only saves the new item, from the third time on, it starts duplicating.
If you need me to elaborate more, just let me know, I thank you all in advance...
Quick and dirty using jQuery:
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
You tagged vue.js but this problem statement is more like from JavaScript side. Basically, You are doing shallow copy of the newItems array into the newlyCreatedItems array which causing the updation issue as both are referencing to the same pointer address.
You can resolve this issue by deep copying with the help of structuredClone() method.
Live Demo :
let newItems = [
{ id: "123", name: "a", otherProps: "someProps" },
{ id: "456", name: "ab", otherProps: "someProps" },
{ id: "789", name: "c", otherProps: "someProps" }
];
let newlyCreatedItems = [];
function saveNewSettings() {
if (!newlyCreatedItems.length ) {
newlyCreatedItems = structuredClone(newItems);
} else {
for ( let i = 0; i < newItems.length; i++ ) {
for ( let j = 0; j < newlyCreatedItems.length; j++ ) {
if ( newItems[i].id == newlyCreatedItems[j].id ) {
newItems.splice( i, 1 );
}
}
}
}
}
saveNewSettings();
console.log(newlyCreatedItems);
console.log(newItems);
console.log('-------');
saveNewSettings();
console.log(newlyCreatedItems);
console.log(newItems);

Find data from the strings of array2 in the array1 and save as new uniq array

I want to find strings that has data from the strings from the array 2 in the array1 and save result as separate uniq array.
As can you see I search for not exact values. From the array1 values I know only part of the information, and I want to find the complete strings, with that information, in array1. And at the end I want to save what I found. So, I don't have a problem with finding here, but a problem with saving in the valid single JSON.
Array examples:
Array #1:
{
"overflow": [
"id:address:name:location:email",
...
"id2:address2:name2:location2:email2"
]
}
Array #2:
[
"location:email",
...
"location2:email2"
]
Code:
resultArr: function() {
var arr1 = '/var/log/1.json';
var arr2 = '/var/log/2.json';
var arrResult = '/var/log/result.json';
var arr2Obj = JSON.parse(fs.readFileSync(arr2, 'utf-8'));
for (var i = 0; i < arr2Obj.length; i++) {
var arr1Obj = JSON.parse(fs.readFileSync(arr1, 'utf-8'));
arr1Obj.overflow = arr1Obj.overflow.filter(function(e) {
return e.includes(arr2Obj[i])
});
fs.appendFile(arrResult, JSON.stringify(arr1Obj, null, 2), 'utf-8');
}
}
My result:
[{
"overflow": [
"id:address:name:location:email"
]
}{
"overflow": [
"id54:address54:name54:location54:email56"
]
}{
"overflow": [
"id2:address2:name2:location2:email2",
"id6:address6:name6:location2:email2"
]
}
What I really want:
{
"overflow": [
"id:address:name:location:email",
"id54:address54:name54:location54:email56",
"id6:address6:name6:location2:email2",
"id2:address2:name2:location2:email2"
]
}
Instead of reading the file again and again, and appending to the result repeatedly, just do both actions only once. All the rest should happen in memory.
You will also get better results (no risk for duplicates in result) when you swap the loops: put the filter action as the outer loop. For the inner loop you can use some, since one match is enough for the entry to be included:
resultArr: function() {
var arr1 = '/var/log/1.json',
arr2 = '/var/log/2.json',
arrResult = '/var/log/result.json',
arr2Obj = JSON.parse(fs.readFileSync(arr2, 'utf-8')),
arr1Obj = JSON.parse(fs.readFileSync(arr1, 'utf-8'));
arr1Obj.overflow = arr1Obj.overflow.filter(function(e) {
return arr2Obj.some(function (f) {
return e.includes(f)
});
});
fs.writeFileSync(arrResult, JSON.stringify(arr1Obj, null, 2), 'utf-8');
}
At each iteration, you're creating a new object and appening it to a file.
JSON is not a good format to append to.
You're replacing the array instead of adding fields to it.
You can do it that way, it should work :
resultArr: () => {
let arr1 = '/var/log/1.json';
let arr2 = '/var/log/2.json';
let arrResult = '/var/log/result.json';
let arr2Obj = JSON.parse(fs.readFileSync(arr2, 'utf-8'));
let arr1Obj = JSON.parse(fs.readFileSync(arr1, 'utf-8')); // reading only one time
arr1Obj.overflow = arr2Obj.map(value => {
return arr1Obj.overflow.filter(e => return e.includes(value))
});
fs.writeFileSync(arrResult, JSON.stringify(arr1Obj, null, 2), 'utf-8'); //Writing only one time
}
Array.map() executes the closure for each field in your array and group all the values returned by the closure in another array.
I also replaced some keywords to make your code more ES6 compliant. I you really want to append, you should use CSV and not JSON.

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);
}
}

jQuery getJSON print array where name is string

I have some good practice with using php and mysql, and am now starting with JSON.
I've made a simple json index containing paths to folders, and items inside them:
{ "foldery" : [
{
"foName": "website/img/bg",
"files" : [
"website/img/bg/bg1.jpeg",
"website/img/bg/bg2.jpg",
"website/img/bg/bg3.jpg",
"website/img/bg/bg4.jpeg"
]
},
{
"foName": "website/img/post1",
"files" : [
"website/img/post1/a.jpeg",
"website/img/post1/b.jpg",
"website/img/post1/c.jpeg",
"website/img/post1/d.jpg"
]
}
]
}
Now here is my jquery, for now returning a big mess of data, somewhere including the info about the contents inside:
$.getJSON("nameindex.json", function(data) {
console.log(data);
});
What I would like it to do, in mySql looks like this:
SELECT files FROM foldery WHERE foName = "website/img/post1"
Thus the result, would be an array containing all the files inside post1.
Unfortunately tho, after 2 hours of attempts I have nothing more than the simple console.log code.
Any help would be gladly appreciated
You need to iterate over your array and check forName to equality
function select(o, foName) {
var length = o.foldery.length;
for(var i = 0; i < length; i++){
if (o.foldery[i].foName == foName) {
return o.foldery[i].files;
}
}
}
var result = select(o, "website/img/post1")
console.log(result);
Result
[ 'website/img/post1/a.jpeg',
'website/img/post1/b.jpg',
'website/img/post1/c.jpeg',
'website/img/post1/d.jpg' ]

Get all string values from a nested object

I have an object, with nested objects. How do I target a specific index of the object and loop through all the nested values of image. As you will note the length of the nested objects vary.
Target example: productArray[0].image = test1.png, test2.png, test3.png
var products = [
//item1
{
identifier: "item-0",
image: {
"img1": "test1.png",
"img2": "test2.png",
"img3": "test3.png"
}
},
//item2
{
identifier: "item-1",
image: {
"img1": "test1.png",
"img2": "test2.png"
}
},
//item3
{
identifier: "item-2",
image: {
"img1": "test1.png",
"img2": "test2.png",
"img3": "test3.png",
"img4": "test4.png",
"img5": "test5.png",
"img6": "test6.png",
"img7": "test7.png"
}
}
];
We can do this. What you need to do is a simple loop through the object at a specific index, or you can target them all. Note that the image object is not an array, so it will not have an accurate length property.
Target all indexes:
for(var i = 0; i < products.length; i++) {
console.log("Item: " + i);
var images = products[i].image;
for(var a in images)
console.log(images[a]);
}
Target specific:
for(var i in products[0].image)
console.log(products[0].image[i]);
I used a for loop here, but you can use a while loop if you would like.
example
Steps:
You need to iterate over your original array of products. products
Each element (product) will be in format { identifier: "", image : {"img1" : "img2", ..}} products[i]
You get the image property of current product - this is an object. products[i].image
Now you need to iterate over the properties of the image object. products[i].image[j]
Code:
for(var i = 0; i < products.length; i++)
{
for(var j in products[i].image)
{
// Here you have all the images for the current product.
// You can print them, group them or whatever you want to do with them
console.log(products[i].image[j]);
}
}
Also you can change the code (introduce variables) to be more readable.
var strs = (function( obj ) {
var ret = [];
for( im in obj ) {
ret.push( obj[im] );
//You could access each image URL here
//ad strs in the end will have all of them
//comma-separated after this code completes
// im is the key, obj[ im ] the value
}
return ret.join(',');
})( products[0].image );
console.log( strs );
WORKING JS FIDDLE DEMO
Here is another way of doing this, with newer functions in ECMAScript 5
var images = Object.keys(products[2].image).map(function(key){
return products[2].image[key]
})
console.log(images) // Returns: ["test1.png", "test2.png", "test3.png", "test4.png", "test5.png", "test6.png", "test7.png"]
How It Works:
Object#keys returns an array of key names. Array#map creates a new array using the keys from Object#keys. By looking up the key from the object you get the value, which will be the image name.
JS FIDDLE

Categories