Programmatically flatten yaml with typescript - javascript

Example input (generally, any yaml file with any content)
some:
nested:
entry: paris
another:
nested:
entry: tel-aviv
Desired output:
Map(2) {
'.some.nested.entry' => 'paris',
'.another.nested.entry' => 'tel-aviv'
}
I tried to use recursion, but failed to define the base case (the function atom):
import { readFileSync } from 'fs'
import { parse, stringify } from 'yaml'
const yamlData = parse(readFileSync('input.yaml').toString())
// An ugly hack to demonstrate what I want to achieve
function atom(yamlData): boolean {
const stringified = stringify(yamlData)
const distance = stringified.search('entry')
const atomicity = distance === 0;
return stringified.search('entry') < 3 // obviously not the way ...
}
function flatten(yamlData, prefix: string): Map<string, any>
{
const flattened = new Map<string, any>();
for (const key in yamlData) {
if (atom(yamlData[key])) {
const abskey = prefix + "." + key;
const value = stringify(yamlData[key]);
const a = value.split(": ")[0].trim() // <--- ugly ...
const b = value.split(": ")[1].trim() // <--- ugly ...
flattened.set(abskey + "." + a, b)
continue;
}
const flattenedTag = flatten(yamlData[key], prefix + "." + key)
for (const [keyTag, value] of flattenedTag.entries()) {
flattened.set(keyTag, value)
}
}
return flattened
}
const flattened = flatten(yamlData, "");
console.log(flattened)

Related

Parse to string a JS function as an Object value

Looking for a solution on parsing to string a JS function as an Object value.
All ideas are welcomed!
The final result would be a string config that i need to pass to a 3-rd party library (this is just a small part).
CodeSandBox Example
import { TEST } from "./config";
function objToString(obj) {
return Object.entries(obj).reduce((cstring, [key, value]) => {
// creates a string in the format 'key: value, key2: value2, etc...'
return cstring !== "" ? `${cstring}, ${key}: ${value}` : `${key}: ${value}`;
}, "");
}
export default function App() {
let configString = objToString({
onChangeBrand: function () {
const test = TEST.Success;
return test;
}
});
configString = `var configString = {
${configString}
}`;
console.log(configString);
return <p>configObj = `{configString}`</p>;
}
Current output
var configString = {
onChangeBrand: function () {
const test = _config.TEST.Success;
return test;
}
}
Desired output
var configString = {
onChangeBrand: function () {
const test = "success";
return test;
}
}
Not 100% sure but with a small change in onChangeBrand and the function call of aciConfig i think i've got your desired output:
import { TEST } from "./config";
function objToString(obj) {
return Object.entries(obj).reduce((cstring, [key, value]) => {
// creates a string in the format 'key: value, key2: value2, etc...'
return cstring !== "" ? `${cstring}, ${key}: ${value}` : `${key}: ${value}`;
}, "");
}
export default function App() {
const fun = () => {
const test = TEST.Success;
return test;
};
const vari = TEST;
const aciConfig = (v) => {
return {
onChangeBrand: function () {
const ss = v.Success.toString();
const rr = fun();
return {ss, rr}
}
};
};
let configObj = aciConfig(vari).onChangeBrand()
let configString = objToString(configObj);
console.log(typeof configObj.ss)
console.log(configObj.ss)
configString = `var configString = {
${configString}
}`;
console.log(configString);
return <p>configObj = `{configString}`</p>;
}

How to write a function which represents an object lookup path

Write a function that takes a object and a string, which represents an object lookup path, for example "something1.something2". The function should return the value on the specific path.
Example:
const lookup = (obj, path) => {....}
const obj = { something1: { something2: "Mouse", something3: 'Cat' } };
const path = 'something1.something2'
console.log(lookup(obj, path));
Result:
'Mouse'
You can use split and then reference the property dynamically using square brackets
const lookup = (obj, path) => {
const paths = path.split('.');
return obj[paths[0]][paths[1]];
}
const obj = { something1: { something2: "Mouse", something3: 'Cat' } };
const path = 'something1.something2'
console.log(lookup(obj, path));

find the next path from the list

How can i reduce this logic into much more better one like a reusable hook function, where i can pass the current path and the tabsDetails and it returns me the next path so I can push to the next route once user clicks on next button
const tabsDetails = [
{
path: "/user-details",
},
{
path: "/service-details",
},
{
path: "/previous-experience-details",
},
{
path: "/documents",
},
];
const allowNextRoute = () => {
// early return if tabsDetails is having falsy values or length is 0
if (!tabsDetails?.length) return
const { pathname } = useLocation() //'details/11012/user-details'
const currentPathsSplitted = pathname?.split("/")
const currentPath = currentPathsSplitted.reverse()[0]
const filteredPaths = tabsDetails.map(({ path }) => path)
let currentPathIndex = filteredPaths.indexOf(`/${currentPath}`)
let nextPathIndex = ++currentPathIndex % filteredPaths.length
let nextPath = filteredPaths[nextPathIndex]
currentPathsSplitted.reverse().pop()
currentPathsSplitted.push(nextPath.split("/").reverse()[0])
history.push(currentPathsSplitted.join("/")) // 'details/11012/service-details
}
You can use pop and findIndex to reduce the array gymnastics:
const tabsDetails = [{path: "/user-details"},{path: "/service-details"},{path: "/previous-experience-details"},{path: "/documents"}];
const delim = "/";
const allowNextRoute = (journey, resource) => {
if (!journey) return;
const parts = resource.split(delim);
const current = `${delim}${parts.pop()}`;
const path = parts.join(delim);
const currIndex = journey.findIndex(o => o.path === current);
const test = (currIndex < journey.length - 1) && (currIndex > -1);
return test ? `${path}${journey[currIndex + 1].path}` : "";
}
let path;
console.log("Test 1 - OP");
path = "details/11012/user-details"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute(tabsDetails, path);
}
console.log("Test 2 - no matching path");
path = "details/11012/user-details-error"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute(tabsDetails, path);
}
console.log("Test 3 - no details");
path = "details/11012/user-details"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute([], path);
}
console.log("Test 4 - details with no match");
path = "details/11012/user-details"; // useLocation()
while (path) {
console.log(path);
path = allowNextRoute([{path: "/foo"}], path);
}
I would personally start with transforming the structure into one that supports the logic you attempt to deploy.
Instead of using an array you might want to transform the current data in a more usable structure that simplifies path lookup and finding the next item. One such structure could be a "linked list" where you have a "node" with the structure:
{ item: item, next: nextNode }
Then use an object to make lookups a specific paths fast:
const tabDetailsLookup = {
"/user-details": {
item: { path: "/user-details" }
next: -> ref to "/service-details" node
},
"/service-details": {
item: { path: "/service-details" }
next: -> ref to "/previous-experience-details" node
},
// ...
}
You can build this structure using a simple for-loop:
const tabsDetails = [
{ path: "/user-details" },
{ path: "/service-details" },
{ path: "/previous-experience-details" },
{ path: "/documents" },
];
// prepare data structure to fit logic needs
const tabDetailsLookup = (function () {
// place array elements inside nodes
const nodes = tabsDetails.map(item => ({ item }));
const lookup = {};
for (let i = 0; i < nodes.length; ++i) {
// create a reference from the current node to the next node
nodes[i].next = nodes[(i + 1) % nodes.length];
// add the current node to the lookup object
lookup[nodes[i].item.path] = nodes[i];
}
return lookup;
})();
console.log(tabDetailsLookup);
// this structure makes it a lot easier to find the next path
console.log(tabDetailsLookup["/user-details"].next.item.path);
This simplifies:
const filteredPaths = tabsDetails.map(({ path }) => path)
let currentPathIndex = filteredPaths.indexOf(`/${currentPath}`)
let nextPathIndex = ++currentPathIndex % filteredPaths.length
let nextPath = filteredPaths[nextPathIndex]
Into:
const nextPath = tabDetailsLookup[`/${currentPath}`].next.item.path;
You can supplement this with a function that does the other stuff:
function nextPath(path) {
const parts = path.split("/");
if (parts.length == 0) return;
const current = parts.pop();
const node = tabDetailsLookup[`/${current}`];
if (!node) return;
return parts.join("/") + node.next.item.path;
}
const tabsDetails = [
{ path: "/user-details" },
{ path: "/service-details" },
{ path: "/previous-experience-details" },
{ path: "/documents" },
];
// prepare data structure to fit logic needs
const tabDetailsLookup = (function () {
// place array elements inside nodes
const nodes = tabsDetails.map(item => ({ item }));
const lookup = {};
for (let i = 0; i < nodes.length; ++i) {
// create a reference from the current node to the next node
nodes[i].next = nodes[(i + 1) % nodes.length];
// add the current node to the lookup object
lookup[nodes[i].item.path] = nodes[i];
}
return lookup;
})();
function nextPath(path) {
const parts = path.split("/");
if (parts.length == 0) return;
const current = parts.pop();
const node = tabDetailsLookup[`/${current}`];
if (!node) return;
return parts.join("/") + node.next.item.path;
}
console.log(nextPath("details/11012/user-details"));
console.log(nextPath("details/11012/service-details"));
console.log(nextPath("non-existent"));

using JavaScript how do I overwrite a 2D array in a preexisting csv file from nodejs

I am planning on using a csv file to store a simple table.
, for example like this
var table = [["jack","john","bob"],
[15,19,20]
["talkative","boring","funny"],
... ,
... ,
... ,
... ,
["science","math","english"]]
i am looking for a way to replace all the data inside the preexisting csv file with var table
//after replacing all the data inside a csv file with var table
jack,john,bob
15,19,20
talkative,boring,funny
a,b,c
x,y,z
g,h,i
science,math,english
or some thing like cleaning the csv file completely and then storing this var table inside it
const fs = require('fs');
const arrayData = [
[1, 2, 3],
['one', 'two', 'three']
];
const stringData = arrayData.reduce((accOne, array) => {
const str = array.reduce((accTwo, item, index) => {
return accTwo + `${item}${index < array.length - 1 ? ',' : ''}`
}, '');
return accOne + `${str}\n`;
}, '');
fs.writeFileSync('test-report.csv', stringData);
Read the array using csv-parse like
const csv = require('csv-parser')
const fs = require('fs')
const results = [];
fs.createReadStream('data.csv')
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => {
console.log(results);
// [
// { NAME: 'Daffy Duck', AGE: '24' },
// { NAME: 'Bugs Bunny', AGE: '22' }
// ]
});
Modify your array within your script
Convert your final array to CSV Format using the code below
const arr = [["My","Lovelly","Array"],["My","Lovelly","Array"]]
const arrayToCSV = (arr, delimiter = ',', rowdelimiter = '\n') =>
{
let rows = arr.length // returns rows
let cols = arr[0].length ; // returns col
let str = ""
for(let i=0;i<rows;i++){
for(let j=0;j<cols;j++){
if(j==cols-1){
str+=arr[i][j]
str+=rowdelimiter
}else{
str+=arr[i][j]
str+=delimiter
}
}
}
return str
// arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
}
const csvstring = arrayToCSV(arr,",")
console.log(csvstring)
Then write the formatted CSV to the file
Hope that is helpful
EDIT : Full Example :-
CSV File : data.csv
name,age
John Doe,27
Adam Linux,20
NodeJs Script : index.js // or any name
const csv = require('csv-parser')
const fs = require('fs')
const results = [];
const arrayToCSV = (arr, delimiter = ',', rowdelimiter = '\n') => {
let rows = arr.length // returns rows
let str = "name,age\n"
for (let i = 0; i < rows; i++) {
str += arr[i].name
str += delimiter
// row delimiter after the last parameter only
str += arr[i].age
str += rowdelimiter
}
return str
}
fs.createReadStream('data.csv')
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => {
console.log(results);
results[0].name = "John Doe"
results[0].age = "27"
results.push({
name: "Adam Linux",
age: "20"
})
console.log(results);
const csvstring = arrayToCSV(results, ",")
console.log("csv String : " + csvstring)
fs.writeFile('data.csv', csvstring, {
encoding: 'utf8',
flag: 'w'
}, (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
});
you can simply use writeFile in write mode. for your reference:
'use strict'
const { parse } = require('json2csv');
const { writeFile } = require('fs')
function convertJsonCsv() {
const data = [
["jack", "john", "bob"],
[15, 19, 20],
["talkative", "boring", "funny"],
["science", "math", "english"]
]
const csv = parse(data)
writeFile('./path/of/the/file.csv', csv, {
encoding: 'utf8',
flag: 'w'
}, (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
}

Why my recursive function works only for one level?

I am trying to learn how to cope with the objects and arrays and I saw many ways of iterating objects but recursing doesn't work for me and I don't understand why. What am I doing wrong?
I need to loop through an object and just slightly change something in an array. In my case, it's uppercasing the keys
Here is what I've got for now
const development = {
port: 8080,
db: {
username: "jkdfs",
password: "dsfsdg",
name: "kslfjskd",
test: { test: 12, another: 'value' }
},
token_secret: "jfkjdsj",
hash_rounds: "kjsfkljdfkl"
};
function throughObject(obj) {
let collection = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
let value = obj[key];
if (typeof obj[key] !== 'object') {
collection[key.toUpperCase()] = value;
} else {
collection[key.toUpperCase()] = nestedObject(obj[key]);
}
}
function nestedObject(nested) {
const sub = {};
for (const k in nested) {
let v = nested[k];
if (typeof nested[k] !== 'object') {
sub[k.toUpperCase()] = v;
} else {
nestedObject(v);
}
}
return sub;
}
}
return collection;
}
const r = throughObject(development);
console.log(r);
When you're recursively calling the function on an object value, you still need to assign it to the sub object: sub[k.toUpperCase()] = nestedObject(v). Also, you don't need 2 different functions.
const development = {
port: 8080,
db: {
username: "jkdfs",
password: "dsfsdg",
name: "kslfjskd",
test: { test: 12, another: 'value' }
},
token_secret: "jfkjdsj",
hash_rounds: "kjsfkljdfkl"
};
function nestedObject(nested) {
const sub = {};
for (const k in nested) {
const v = nested[k];
if (typeof nested[k] !== 'object')
sub[k.toUpperCase()] = v;
else
sub[k.toUpperCase()] = nestedObject(v); // <- HERE
}
return sub;
}
console.log(nestedObject(development))
Here's a shorter version using Object.fromEntries()
const development={port:8080,db:{username:"jkdfs",password:"dsfsdg",name:"kslfjskd",test:{test:12,another:"value"}},token_secret:"jfkjdsj",hash_rounds:"kjsfkljdfkl"};
const convert = o =>
Object.fromEntries(
Object.entries(o).map(([k, v]) =>
[k.toUpperCase(), Object(v) === v ? convert(v) : v]
)
)
console.log(convert(development))

Categories

Resources