Javascript - Parsing INI file into nested associative array - javascript

I'm new to Javascript and I'm having trouble parsing an INI formatted file into nested objects.
The file I have is formatted like this:
ford.car.focus.transmission=standard
ford.car.focus.engine=four-cylinder
ford.car.focus.fuel=gas
ford.car.taurus.transmission=automatic
ford.car.taurus.engine=V-8
ford.car.taurus.fuel=diesel
purchased=Ford Taurus
I would like to have the structure look like this:
{ ford:
{ car:
{ focus:
{
transmission: 'standard',
engine: 'four-cylinder',
fuel: 'gas'
}
}
{ taurus:
{
transmission: 'automatic',
engine: 'V-8',
fuel: 'diesel'
}
}
}
purchased: 'Ford Taurus'
}
I'm storing the file in lines in an array, splitting on '\n'. I'm trying to write a method that would be called in a loop, passing my global object like this:
var hash = {};
var array = fileData.toString().split("\n");
for (i in array) {
var tmp = array[i].split("=");
createNestedObjects(tmp[0], tmp[1], hash);
}
This should let me access the values in the hash object like:
hash.ford.car.focus.fuel
# 'gas'
hash.purchase
# 'Ford Taurus'
I've tried something like what was suggested here: Javascript: how to dynamically create nested objects using object names given by an array, but it only seems to set the last item in the nest.
{ fuel: 'diesel',
purchased: 'Ford Taurus' }
My unsuccessful attempt looks like this:
createNestedObjects(path, value, obj) {
var keys = path.split('.');
keys.reduce(function(o, k) {
if (k == keys[keys.length-1]) {
return o[k] = value;
} else {
return o[k] = {};
}
}, obj);
}
However, it will only return the last value for the nested values:
{ ford: { car: { taurus: { fuel: 'diesel' } } },
purchased: 'Ford Taurus' }

The function needs to check whether an intermediate key already exists before assigning to it.
createNestedObjects(path, value, obj) {
var keys = path.split('.');
keys.reduce(function(o, k) {
if (k == keys[keys.length-1]) {
return o[k] = value;
} else if (o[k]) {
return o[k];
} else {
return o[k] = {};
}
}, obj);
}

Related

How do I set the value of a key in on array as the key in an other array?

I am trying to get the value feature_1 of a key name from the array data and set feature_1 as the key of another array asset which has an array as value.
Example :
//input:
data: {
name: "feature_1",
value_a: 1,
value_b: 2
}
//Output:
asset: {
feature_1:[1,2]
}
You can try:
var asset = {};
if ('name' in data) {
var tmp = [];
for (k in data){
if (k != 'name') tmp.push(data[k]);
}
asset[data['name']] = tmp;
}
and in case your interpreter supports ES6 you can use, for example:
let {name, ...v} = data;
let asset = {[name]: Object.values(v)};
If supports same keys in a JSON object ( It seems not ) You can do it like this:
let data= {
name: "feature_1",
value_a: 1,
value_b: 2,
value_x: 500,
name: "feature_2",
value_a: 17,
value_b: 21,
value_x: 510
}
console.log(
Object.entries(data).reduce((a,[key,value],index)=>{
if (key==='name')
return {index, assets: {...a.assets, [value]:[]}};
return {
index : a.index,
assets : {
...a.assets,
[Object.entries(a.assets)[a.index][0]] : [...Object.entries(a.assets)[a.index][1],value]
}};
},{index:0,assets:{}}).assets
);
But we know the correct way is using separated array rows.
This can be accomplished the following way:
const obj = {
data: {
name: "feature_1",
value_a: 1,
value_b: 2
}
};
const output = {
assets: {
[obj.data.name]: Object.values(obj.data).filter(el => el !== obj.data.name)
}
}
console.log(output);

Convert JSON object to object which not in JSON format

I have a JSON object with the structure as below
const inputObj = {
"prop1": "val1",
"prop2": {
"prop2_1": "val2_1",
"prop2_2": "val2_2"
}
"prop3": "val3"
}
My objective: I would like to take the property, including the nested property, and store the result in a txt file, but not in JSON format. To make it clear, here is my expected output in the txt file:
{
prop1: {
id: 'prop1'
},
prop2_prop2_1: {
id: 'prop2.prop2_1'
},
prop2_prop2_2: {
id: 'prop2.prop2_2'
}
prop3: {
id: 'prop3'
}
}
So far, I could write the non nested property, but still not in the structure which I expected. Here is the result so far:
{
"prop1": "prop1",
"prop3": "prop3"
}
Its still in JSON format, not in the structure that I expected, and the nested property still not caught (I still thinking how to get it)
here is the code so far to make my current result:
const fs = require('fs')
const fileName = "./results.txt"
function getAllKeys(obj, path = [], result = []) {
Object.entries(obj).forEach(([k, v]) => {
if (typeof v === 'object') getAllKeys(v, path.concat(k), result)
else result.push(path.concat(k).join("."))
})
return result
}
const inputToFile = getAllKeys(inputObj)
// console.log(inputToFile)
// result of the console.log
// prop1
// prop2.prop2_1
// prop2.prop2_2
// prop3
const newObj = {}
for (var i = 0; i < inputToFile.length; i++) {
var input = inputToFile[i]
var dotIndex = input.indexOf('.') // to check if its from the nested JSON property of the inputObj
if (dotIndex === -1) {
// no dot or nested property in the JSON
newObj[input] = input.toString()
} else {
// if the input contain dot, which is a nested JSON
}
}
fs.writeFileSync(fileName, JSON.stringfy(newObj))
// if I use above line, the result in the file is as I had mention above. But, if the code is like below:
const finals = JSON.stringfy(newObj)
fs.writeFileSync(fileName, JSON.parse(finals))
// the output in the file is only "[Object object]" without double quote
Update
The reason why I need the result to be formatted like that, is because I want to use react-intl. I already have the locale file (the translation), which looks like the inputObj (the structure). Then, I need to make a file, which like this (below), so the lib could translate it:
import { defineMessages } from 'react-intl';
const MessagesId = defineMessages({
prop1: {
id: 'prop1'
},
prop2_prop2_1: {
id: 'prop2.prop2_1'
},
prop2_prop2_2: {
id: 'prop2.prop2_2'
},
prop3: {
id: 'prop3'
}
})
export default MessagesId;
Thats why, I need it to be not like JSON. Because I already have thousand codes for the translation, but need to define it in the MessagesId. It would be so much takes time rite if I do it manually .__.
Ps: the react-intl is works, the problem is only the converting as my initial questions
This script can handle multiple levels of nestied object.
const outputObj = {};
const convertNestedObj = (obj, parentKey = []) => {
for (key in obj) {
newParentKey = [...parentKey, key];
if (typeof obj[key] === 'object') {
convertNestedObj(obj[key], newParentKey);
} else {
outputObj[newParentKey.join('_')] = { id: newParentKey.join('_') };
}
}
};
convertNestedObj(inputObj);

Convert dynamic string to JSON using javascript

I am using API that its response contains dynamic array of strings as the following:
var arr = ["Request.name","Request.name.first","Request.name.first.last"]; //response 3
so i want to convert it dynamically to array of JSON objects like this:
var arrayOfObjects = [
{
"Request": {
"name":val
}
} //converted from arr[0]
,{
"Request": {
"name":{
"first":val
}
}
} //converted from arr[1]
,{
"Request": {
"name":{
"first":{
"last":val
}
}
}
} //converted from arr[2]
];
and so on...
is this possible?
You can create a function that converts a dot-separated string into a respective deeply nested object, then map your input array of strings with it. My solution is iterative, though I think it can be done recursively as well.
var arr = ["Request.name","Request.name.first","Request.name.first.last"];
function createObjFromStr(str) {
return str.split('.').reduceRight((all, key, i, arr) => {
if (i === arr.length - 1) {
all = {[key]: 0};
} else {
all[key] = Object.assign({}, all);
Object.keys(all).forEach(k => {
if (k !== key) delete all[k];
});
}
return all;
}, {});
}
const arrayOfObjects = arr.map(createObjFromStr);
console.log(arrayOfObjects);

How to deeply remove keys in object?

I have this json object returned from an API that has a few quirks, and I'd like to normalize it so I can process the input the same for every response. These means getting rid of superfluous keys:
Response:
{
_links: {...},
_embedded: {
foo: [
{
id: 2,
_embedded: {
bar: []
}
}
]
}
}
So I'd like to remove all the _embedded keys and flatten it, like so:
{
_links: {...},
foo: [
{
id: 2,
bar: []
}
]
}
This is what I have at the moment, but it only works for the top level and I don't think it'll play well with arrays.
_.reduce(temp1, function(accumulator, value, key) {
if (key === '_embedded') {
return _.merge(accumulator, value);
}
return accumulator[key] = value;
}, {})
Loop in recursion on all of your keys, once you see a key which start with _
simply remove it.
Code:
var
// The keys we want to remove from the Object
KEYS_TO_REMOVE = ['_embedded'],
// The data which we will use
data = {
_links: {'a': 1},
_embedded: {
foo: [
{
id: 2,
_embedded: {
bar: []
}
},
{
id: 3,
_embedded: {
bar: [
{
id: 4,
_embedded: {
bar: []
}
}
]
}
}
]
}
};
/**
* Flatten the given object and remove the desired keys if needed
* #param obj
*/
function flattenObject(obj, flattenObj) {
var key;
// Check to see if we have flatten obj or not
flattenObj = flattenObj || {};
// Loop over all the object keys and process them
for (key in obj) {
// Check that we are running on the object key
if (obj.hasOwnProperty(key)) {
// Check to see if the current key is in the "black" list or not
if (KEYS_TO_REMOVE.indexOf(key) === -1) {
// Process the inner object without this key
flattenObj[key] = flattenObject(obj[key], flattenObj[key]);
} else {
flattenObject(obj[key], flattenObj);
}
}
}
return flattenObj;
}
console.log(flattenObject(data));
So, basically you already have almost all of the code you need. All we have to do is wrap it in a function so we can use recursion. You'll see we only add a check to see if it is an object, if it is, we already have a function that knows how to flatten that object, so we'll just call it again with the key that we need to flatten.
function flatten(temp1) { // Wrap in a function so we can use recursion
return _.reduce(temp1, function(accumulator, value, key) {
if (key === '_embedded') {
return _.merge(accumulator, value);
} else if (value !== null && typeof value === 'object') // Check if it's another object
return _.merge(accumulator, flatten(value)) // Call our function again
return accumulator[key] = value;
}, {})
}
I'll be able to test it in a bit, but this should be what you need.
Got it!
function unEmbed(data) {
return _.reduce(data, function(accumulator, value, key) {
const returnableValue = _.isObject(value) ? unEmbed(value) : value;
if (key === 'embedded') {
return _.merge(accumulator, returnableValue);
}
accumulator[key] = returnableValue;
return accumulator;
}, {});
}
Problem before I was returning return accumulator[key] = returnableValue, which worked out to be return returnableValue.

Build a JSON object from absolute filepaths

I receive (in my angularjs application) from a server a list of directories like this:
['.trash-user',
'cats',
'cats/css',
'cats/images/blog',
'cats/images/gallery']
And I would like to build a javascript variable which looks like this:
[{
label: '.trash-user'},
{label: 'cats',
children: [{
label: 'css'},
{label: 'images',
children: [{
label: 'blog'},
{label: 'gallery'}
]}
]}
}]
The paths are in random order.
Hope somebody has some really elegant solution, but any solution is appreciated!
Edit:
Here is my naive approach, I have real trouble with recursion.
I could only make level 0 to work:
var generateTree = function(filetree){
console.log('--------- filetree -------');
var model = [];
var paths = [];
for(var i=0;i<filetree.length;i++) {
paths = filetree[i].split('/');
for(var j=0;j<paths.length;++j) {
var property = false;
for(var k=0;k<model.length;++k) {
if (model[k].hasOwnProperty('label') &&
model[k].label === paths[0]) {
property = true;
}
}
if (!property) {
model.push({label: paths[0]});
}
}
}
console.log(model);
};
If you want an elegant solution, lets start with a more elegant output:
{
'.trash-user': {},
'cats': {
'css': {},
'images': {
'blog': {},
'gallery': {},
},
},
}
Objects are much better than arrays for storing unique keys and much faster too (order 1 instead of order n). To get the above output, do:
var obj = {};
src.forEach(p => p.split('/').reduce((o,name) => o[name] = o[name] || {}, obj));
or in pre-ES6 JavaScript:
var obj = {};
src.forEach(function(p) {
return p.split('/').reduce(function(o,name) {
return o[name] = o[name] || {};
}, obj);
});
Now you have a natural object tree which can easily be mapped to anything you want. For your desired output, do:
var convert = obj => Object.keys(obj).map(key => Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key]) } : { label: key });
var arr = convert(obj);
or in pre-ES6 JavaScript:
function convert(obj) {
return Object.keys(obj).map(function(key) {
return Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key])} : { label: key };
});
}
var arr = convert(obj);
I'll venture that generating the natural tree first and then converting to the array will scale better than any algorithm working on arrays directly, because of the faster look-up and the natural impedance match between objects and file trees.
JSFiddles: ES6 (e.g. Firefox), non-ES6.
Something like this should work:
function pathsToObject(paths) {
var result = [ ];
// Iterate through the original list, spliting up each path
// and passing it to our recursive processing function
paths.forEach(function(path) {
path = path.split('/');
buildFromSegments(result, path);
});
return result;
// Processes each path recursively, one segment at a time
function buildFromSegments(scope, pathSegments) {
// Remove the first segment from the path
var current = pathSegments.shift();
// See if that segment already exists in the current scope
var found = findInScope(scope, current);
// If we did not find a match, create the new object for
// this path segment
if (! found) {
scope.push(found = {
label: current
});
}
// If there are still path segments left, we need to create
// a children array (if we haven't already) and recurse further
if (pathSegments.length) {
found.children = found.children || [ ];
buildFromSegments(found.children, pathSegments);
}
}
// Attempts to find a ptah segment in the current scope
function findInScope(scope, find) {
for (var i = 0; i < scope.length; i++) {
if (scope[i].label === find) {
return scope[i];
}
}
}
}

Categories

Resources