Build nested JSON from string of nested keys [duplicate] - javascript

This question already has answers here:
Convert a JavaScript string in dot notation into an object reference
(34 answers)
Closed 5 years ago.
I have csv files that I am reading using nodeJS. I convert each file to text before reading.
Each line in the file have data delimited with =.
Each line looks something like
data.location.degree.text=sometexthere
The first portion before the "=" represents an index to a JSON object in my app. My aim is to parse this data and build a JSON representation of it so that the line above becomes
data:{
location:{
degree:{
text: 'sometexthere'
}
}
}
Using javascript/nodejs; How can I convert a string which is supposed to represent a sequence of nested JSON keys, into a JSON object like above?

You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
path = path.replace(/[\[]/gm, '.').replace(/[\]]/gm, ''); //to accept [index]
var keys = path.split('.'),
last = keys.pop();
keys.reduce(function (o, k) { return o[k] = o[k] || {}; }, object)[last] = value;
}
var data = {};
setValue(data, 'location.degree.text', 'sometexthere');
console.log(data);

// result container
var res = {};
// input data
var inp = [
'data.location.degree.text=sometexthere',
'data.otherLocation.degree.otherText=foo',
'data.location.degree.otherText=bar',
'we.can.handle.values.that.are_undefined=',
'we.can.handle.values.that.contain_equals_signs=yes=we=can'
];
// recursive function
var pathToObject = function(resultReference, path)
{
// split path on dots
// e.g. data.location.degree.text=sometexthere
// -> ["data", "location", "degree", "text=sometexthere"]
var splitPathParts = path.split('.');
// if there is only one part, we're at the end of our path expression
// e.g. ["text=sometexthere"]
if (splitPathParts.length === 1){
// split "text=sometexthere" into ["text", "sometexthere"]
var keyAndValue = splitPathParts[0].split('=');
// set foo = bar on our result object reference
resultReference[keyAndValue.shift()] = keyAndValue.join('=');
return;
}
// the first element of the split array is our current key
// e.g. for ["data", "location", "degree", "text=sometexthere"],
// the currentKey would be "data";
var currentKey = splitPathParts.shift();
// if our object does not yet contain the current key, set it to an empty object
resultReference[currentKey] || (resultReference[currentKey] = {});
// recursively call ourselves, passing in
// the nested scope and the rest of the path.
// e.g. { data : {} } and 'location.degree.text=sometexthere'
pathToObject(resultReference[currentKey], splitPathParts.join('.'));
}
for (var i = 0; i < inp.length; i++)
{
pathToObject(res, inp[i]);
}
console.log(res);
ES6 syntax makes things slightly more terse:
'use strict';
const pathToObject = (resultReference, path) => {
let [currentKey, ...restOfPath] = path.split('.');
if (restOfPath.length === 0) {
let [k, ...v] = currentKey.split('=');
resultReference[k] = v.join('=');
return;
}
resultReference[currentKey] || (resultReference[currentKey] = {});
pathToObject(resultReference[currentKey], restOfPath.join('.'));
}
let res = {};
[
'data.location.degree.text=sometexthere',
'data.otherLocation.degree.otherText=foo',
'data.location.degree.otherText=bar',
'we.can.handle.values.that.are_undefined=',
'we.can.handle.values.that.contain_equals_signs=yes=we=can'
].forEach(x => pathToObject(res, x));
console.log(res);

Related

How to group all values that have the same key into an array

First, sorry if you find the question confusing.
Basically, I have an object like this:
[{"6":6.5},{"4":4.2},{"6":6.3}]
What I want to do, is to remove the duplicated keys but keep there values and push them all into one unique key only, as an array. like this:
[{"6":[6.5, 6.3]}, {"4": 4.2}]
Can anyone suggest a solution?
.reduce() is what you want:
var data = [{"6":6.5},{"4":4.2},{"6":6.3}];
var res = data.reduce((rv, obj) => {
var key = Object.keys(obj)[0];
rv[key] = rv[key] || [];
rv[key].push(obj[key]);
return rv;
}, {});
console.log(res);
Note: This returns data always in the format of arrays (Even if there is one value). If you're looking for the exact format you specified, you just need to add more logic as I've demonstrated below (Although, I wouldn't recommend this approach, as it adds more complication down the line.)
var data = [{"6":6.5},{"4":4.2},{"6":6.3}];
var res = data.reduce((rv, obj) => {
var key = Object.keys(obj)[0];
if (Array.isArray(rv[key])) { // Already is an array
rv[key].push(obj[key]);
} else if (rv[key] !== undefined) { // Has a value. Convert to array
rv[key] = [rv[key], obj[key]];
} else { // Haven't seen this key yet. Set the value
rv[key] = obj[key];
}
return rv;
}, {});
console.log(res);

Convert JavaScript dotted string in an object [duplicate]

This question already has answers here:
How do I split a string, breaking at a particular character?
(17 answers)
Generic solution to create an Object of unknown deepth from an Array
(1 answer)
Closed 5 years ago.
Given a JS string: var s = "deep.deeper.deepest", how can I convert this into object like this: deep: {deeper: {deepest: {}}}
const dottedToObj = (str, orig = {}) => (str.split(".").reduce((obj, key) => obj[key] = {}, orig), orig);
Just reduce the array of strings (splitted the original string) into a chain of objects. Or a bit less functional:
function dottedToObj(str){
const root = {};
var acc = root;
for(const key of str.split(".")){
acc = acc[key] = {};
}
return root;
}
A simple loop should work for this, just move through each dotted property while moving down one level in the object:
const s = "deep.deeper.deepest";
function convertToObject(str) {
const result = {};
let inner = result;
for (const key of s.split(".")) {
// Give the object a child with this key
inner[key] = {};
// Set the current object to that child.
inner = inner[key]
}
// Return the original
return result;
}
console.log(convertToObject(s))

How to detect circular references in JavaScript

For example:
$ node
> var x = {}
undefined
> x.x = x
{ x: [Circular] }
Wondering the sort of structures are they using to accomplish this, because it's not encoded directly into what I just did. It seems like they would do something like:
var graph = new Graph(object)
graph.detectCircularReferences()
And then it would get them, but not sure how that works. Hoping to learn how to implement that.
Taking into an account the ideas from the comments I came to this function. It traverses the passed object (over arrays and object) and returns an array of paths that point to the circular references.
// This function is going to return an array of paths
// that point to the cycles in the object
const getCycles = object => {
if (!object) {
return;
}
// Save traversed references here
const traversedProps = new Set();
const cycles = [];
// Recursive function to go over objects/arrays
const traverse = function (currentObj, path) {
// If we saw a node it's a cycle, no need to travers it's entries
if (traversedProps.has(currentObj)) {
cycles.push(path);
return;
}
traversedProps.add(currentObj);
// Traversing the entries
for (let key in currentObj) {
const value = currentObj[key];
// We don't want to care about the falsy values
// Only objects and arrays can produce the cycles and they are truthy
if (currentObj.hasOwnProperty(key) && value) {
if (value.constructor === Object) {
// We'd like to save path as parent[0] in case when parent obj is an array
// and parent.prop in case it's an object
let parentIsArray = currentObj.constructor === Array;
traverse(value, parentIsArray ? `${path}[${key}]` : `${path}.${key}`);
} else if (value.constructor === Array) {
for (let i = 0; i < value.length; i += 1) {
traverse(value[i], `${path}.${key}[${i}]`);
}
}
// We don't care of any other values except Arrays and objects.
}
}
}
traverse(object, 'root');
return cycles;
};
Then you can test it like this:
// Making some cycles.
const x = {};
x.cycle = x;
const objWithCycles = {
prop: {},
prop2: [1, 2, [{subArray: x}]]
};
objWithCycles.prop.self = objWithCycles;
console.log(getCycles(objWithCycles));
It produces the following output which is a list of cycles in the object:
[ 'root.prop.self', 'root.prop2[2][0].subArray.cycle' ]

Remove empty brackets from a JSON object

I would like to remove the matching elements {}, and {} from a JSON string.
Input : "test": [{},{},{},{},{},{},{}],
Output : "test": [],
To do so, I tried :
var jsonConfig = JSON.stringify(jsonObj);
var jsonFinal = jsonConfig.replace(/[{},]/g, ''); // Remove global
var jsonFinal = jsonConfig.replace(/[{},]/, ''); // Remove brackets
console.log(jsonFinal);
and many more.
How can I remove only those set of elements from my JSON without impacting the other brackets and comma?
Do NOT attempt to modify JSON with string manipulation functions.
ALWAYS parse the JSON, transform the data, and re-stringify to JSON.
EDIT: this answer addresses your comment that the input data object will contain other potential keys that should be present in the output.
// a couple of procedures to help us transform the data
const isEmptyObject = x => Object.keys(x).length === 0;
const not = x => ! x;
const comp = f => g => x => f (g (x));
const remove = f => xs => xs.filter (comp (not) (f));
// your input json
let json = '{"test": [{},{},{"x": 1}], "test2": [{},{}], "a": 1, "b": 2}';
// parsed json
let data = JSON.parse(json);
// transform data
let output = JSON.stringify(Object.assign({}, data, {
// remove all empty objects from `test`
test: remove (isEmptyObject) (data.test),
// remove all empty objects from `test2`
test2: remove (isEmptyObject) (data.test2),
}));
// display output
console.log(output); // '{"test":[{"x":1}],"test2":[],"a":1,"b":2}'
I like the ES2015 answer of #naomik.
This is another alternative:
/**
* Remove empty objects or arrays
* #param {Object, Array} obj: the object to which remove empty objects or arrays
* #return {Any}
*/
const removeEmptyObject = (function() {
const isNotObject = v => v === null || typeof v !== "object";
const isEmpty = o => Object.keys(o).length === 0;
return function(obj) {
if (isNotObject(obj)) return obj;
if (obj instanceof Array) {
for (let i = 0; i < obj.length; i += 1) {
if (isNotObject(obj[i])) continue;
if (isEmpty(obj[i])) obj.splice(i--, 1);
else obj[i] = removeEmptyObject(obj[i]);
}
}
else {
for (let p in obj) {
if (isNotObject(obj[p])) continue;
if (!isEmpty(obj[p])) obj[p] = removeEmptyObject(obj[p]);
if (isEmpty(obj[p])) delete obj[p];
}
}
return obj;
}
}());
Now lets test the code:
var json = '{"test": [{},{},{"x": 1}], "test2": [{},{}], "test3":[[],[1,2,3],[]], "a": 1, "b": 2}';
var data = JSON.parse(json); //Object
var output = removeEmptyObject(data);
console.log(output);
console.log(removeEmptyObject(9));
console.log(removeEmptyObject(null));
console.log(removeEmptyObject({}));
You should work on the actual object not the string.
If you do, you can loop through the object and check if it has any properties. If it doesn't have any, you can remove it.
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
//remove here
}
}
Setting aside the question of whether string manipulation is the best way to tidy up JSON data, your earlier attempts would remove all braces and commas, because [] in a regexp indicates "match any of the characters contained inside these brackets". If you were trying to treat those as literal characters, they'd need to be escaped: \[ or \]
You want something like .replace(/{},?/g,"") (which means "match all instances of the string {} or the string {}, -- the question mark makes the preceding character an optional match).
(This would, of course, remove all empty objects from the string, and has the potential to create invalid JSON given input like "foo: {}, bar: {}" -- so only use this if you're certain that your data will never include intentionally empty objects.)

convert CSV lines into Javascript objects

I have a simple csv file
people.csv:
fname, lname, uid, phone, address
John, Doe, 1, 444-555-6666, 34 dead rd
Jane, Doe, 2, 555-444-7777, 24 dead rd
Jimmy, James, 3, 111-222-3333, 60 alive way
What I want to do it get each line of the CSV, convert it to a JavaScript object, store them into an array, and then convert the array into a JSON object.
server.js:
var http = require('http');
var url = require('url');
var fs = require('fs');
var args = process.argv;
var type = args[2] || 'text';
var arr = [];
var bufferString;
function csvHandler(req, res){
fs.readFile('people.csv',function (err,data) {
if (err) {
return console.log(err);
}
//Convert and store csv information into a buffer.
bufferString = data.toString();
//Store information for each individual person in an array index. Split it by every newline in the csv file.
arr = bufferString.split('\n');
console.log(arr);
for (i = 0; i < arr.length; i++) {
JSON.stringify(arr[i]);
}
JSON.parse(arr);
res.send(arr);
});
}
//More code ommitted
My question is if I am actually converting that CSV lines into Javascript objects when I call the .split('\n') method on bufferString or is there another way of doing so?
By doing this:
arr = bufferString.split('\n');
you will have an array containing all rows as string
["fname, lname, uid, phone, address","John, Doe, 1, 444-555-6666, 34 dead rd",...]
You have to break it again by comma using .split(','), then separate the headers and push it into an Javascript Object:
var jsonObj = [];
var headers = arr[0].split(',');
for(var i = 1; i < arr.length; i++) {
var data = arr[i].split(',');
var obj = {};
for(var j = 0; j < data.length; j++) {
obj[headers[j].trim()] = data[j].trim();
}
jsonObj.push(obj);
}
JSON.stringify(jsonObj);
Then you will have an object like this:
[{"fname":"John",
"lname":"Doe",
"uid":"1",
"phone":"444-555-6666",
"address":"34 dead rd"
}, ... }]
See this FIDDLE
Using ES6/ES7 and some functional programming guidelines:
All variables are const (immutability)
Use map/reduce instead of while/for
All functions are Arrow
No dependencies
// Split data into lines and separate headers from actual data
// using Array spread operator
const [headerLine, ...lines] = data.split('\n');
// Split headers line into an array
// `valueSeparator` may come from some kind of argument
// You may want to transform header strings into something more
// usable, like `camelCase` or `lowercase-space-to-dash`
const valueSeparator = '\t';
const headers = headerLine.split(valueSeparator);
// Create objects from parsing lines
// There will be as much objects as lines
const objects = lines
.map( (line, index) =>
line
// Split line with value separators
.split(valueSeparator)
// Reduce values array into an object like: { [header]: value }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
.reduce(
// Reducer callback
(object, value, index) => ({
...object,
[ headers[index] ]: value,
}),
// Initial value (empty JS object)
{}
)
);
console.log("Objects:", objects);
For CSV files using , as separator and quotes string values, you can use this version:
// Split data into lines and separate headers from actual data
// using Array spread operator
const [headerLine, ...lines] = data.split('\n');
// Use common line separator, which parses each line as the contents of a JSON array
const parseLine = (line) => JSON.parse(`[${line}]`);
// Split headers line into an array
const headers = parseLine(headerLine);
// Create objects from parsing lines
// There will be as much objects as lines
const objects = lines
.map( (line, index) =>
// Split line with JSON
parseLine(line)
// Reduce values array into an object like: { [header]: value }
.reduce(
(object, value, index) => ({
...object,
[ headers[index] ]: value,
}),
{}
)
);
return objects;
Note: For big files, it would be better to work with streams, generators, iterators, etc.
You could try using MVC Razor,
<script type="text/javascript">
MyNamespace.myConfig = #Html.Raw(Json.Encode(new MyConfigObject()));
</script>
The Json.Encode will serialize the initialized object to JSON format. Then the Html.Raw will prevent it from encoding the quotes to ".
Here the entire example
You can use lodash (or underscore) to help with this.
var objects = _.map(arr, function(item){return item.split(',');});
var headers = objects[0];
objects.splice(0, 1); // remove the header line
populatedObject = [];
objects.forEach(function(item){
var obj = _.zipObject(headers, item);
populatedObject.push(obj);
});
The .zipObject method will match each header to each value in the items array and produce an object.
Here is a solution if you already have an array and want that the csv header (first line) to be the object's property.
const csvArrayToObj = (csvData) => {
return csvData
.map((csvLine, csvIndex) => {
if (csvIndex === 0 || !csvLine.length) return null; // skip header and empty lines
return csvLine.reduce((a, v, i) => ({ ...a, [csvData[0][i]]: v }), {});
})
.filter((filter) => !!filter); //filter empty lines
};
const csvArray = [
['name', 'age'],
['John Doe', 20],
['Jane Doe', 30],
];
csvArrayToObj(csvArray);
// output
[
{
"name": "John Doe",
"age": 20
},
{
"name": "Jane Doe",
"age": 30
}
]

Categories