Convert JSON array and nested data to CSV - javascript

I have an array of JSON objects that I'm trying to convert to CSV where the end format would look something like this in excel:
url
id
type
http://www.example.com
F22
a11y-true
C30
a11y-unknown
RC30
a11y-true
http://www.example.com/something-else
F22
a11y-true
F23
a11y-true
U10
a11y-unknown
CR14
a11y-unknown
RH03
a11y-true
given data such as...
[
{
"url": "https://www.example.com/something",
"ids": [
{ "id": "F22", "type": "a11y-true" },
{ "id": "C30", "type": "a11y-unknown" }
]
},
{
"url": "https://www.example.com/something-else",
"ids": [
{ "id": "F23", "type": "a11y-true" },
{ "id": "U10", "type": "a11y-unknown" },
{ "id": "CR14", "type": "a11y-unknown" },
{ "id": "RH03", "type": "a11y-true" }
]
}
]
I tried converting the JSON using:
const items = data
const header = Object.keys(items[0])
const csv = [header.join(','),...items.map(row => header.map(fieldName => JSON.stringify(row[fieldName])).join(','))].join('\r\n')
console.log(csv)
but I end up with only the first level converted over:
"https://www.example.com/something",[{"id":"F22","type":"a11y-true"},{"id":"C30","type":"a11y-unknown"}]
"https://www.example.com/something-else",[{"id":"F23","type":"a11y-true"},{"id":"U10","type":"a11y-unknown"},{"id":"CR14","type":"a11y-unknown"},{"id":"RH03","type":"a11y-true"}]
How can I also get the nested arrays converted to CSV so that each {} is a single entry?

There are libraries for working with csv in js but you could just define header fields and then pick those field values when you loop over ids
const data = [{"url":"https://www.example.com/something","ids":[{"id":"F22","type":"a11y-true"},{"id":"C30","type":"a11y-unknown"}]},{"url":"https://www.example.com/something-else","ids":[{"id":"F23","type":"a11y-true"},{"id":"U10","type":"a11y-unknown"},{"id":"CR14","type":"a11y-unknown"},{"id":"RH03","type":"a11y-true"}]}]
const separator = ', '
const header = ["url", "id", 'type']
const body = data.reduce((r, { url, ids }) => {
ids.forEach((e, i) => {
const first = i === 0 ? url : ''
const other = header.slice(1).map(k => e[k]).join(separator)
r.push([first, other].join(separator))
})
return r
}, [])
const result = `${header.join(separator)}\n${body.join('\n')}`
console.log(result)

Related

How parse JSON with complex nesting and unnamed array?

I am trying to figure out how to parse the JSON response I receive when I make a call to a specific database (JSON response shown below) using vanilla javascript - and so far I have not had any luck. I am placing an API call to the Quickbase database and they have a standard formatting for their JSON response. The API i am calling can be found at this link: https://developer.quickbase.com/operation/runQuery.
Here is what a response from the API call looks like
{
"data": [
{
"6": {
"value": 11.0
},
"69": {
"value": "A"
},
"70": {
"value": "B"
}
},
{
"6": {
"value": 11.0
},
"69": {
"value": "C"
},
"70": {
"value": "D"
}
}
],
"fields": [
{
"id": 6,
"label": "Related Invoice",
"type": "numeric"
},
{
"id": 69,
"label": "TEST1",
"type": "text"
},
{
"id": 70,
"label": "TEST2",
"type": "text"
}
],
"metadata": {
"numFields": 3,
"numRecords": 2,
"skip": 0,
"totalRecords": 2
}
}
And this is what I want to parse it into (do NOT need to omit the JSON not shown here - I just did that for clarity)
{
"data": [
{
"Related Invoice":11.0,
"TEST1":"A",
"TEST2":"B"
},
{
"Related Invoice":11.0,
"TEST1":"C",
"TEST2":"D"
}
]
}
Below is the full javascript code i am using
let headers = {
'QB-Realm-Hostname': 'XXXXXX',
'User-Agent': 'Invoice',
'Authorization': 'XXXXXX',
'Content-Type': 'application/json'
}
let body =
{
"from": "bq2paydp2",
"select": [
6,
69,
70
],
"where": "{6.EX.11}",
"sortBy": [
{
"fieldId": 6,
"order": "ASC"
},
{
"fieldId": 69,
"order": "ASC"
}
]
}
const xmlHttp = new XMLHttpRequest();
xmlHttp.open('POST', 'https://api.quickbase.com/v1/records/query', true);
for (const key in headers) {
xmlHttp.setRequestHeader(key, headers[key]);
}
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState === XMLHttpRequest.DONE) {
console.log(xmlHttp.responseText);
let line_items = JSON.parse(this.responseText, dataReviver);
console.log(line_items);
//function dataReviver (key, value) {
//if (key = 6)
// {
// var newHeaderName = 99;
// return newHeaderName;
// }
//
// return value;
//}
//document.getElementById('abc').innerHTML = line_items.data[0][6].value;
function generateTableHead(table,tableData) {
let thead = table.createTHead();
let row = thead.insertRow();
for (let key of tableData) {
let th = document.createElement("th");
let text = document.createTextNode(key);
th.appendChild(text);
row.appendChild(th);
}
};
function generateTable(table, tableData) {
for (let element of tableData) {
let row = table.insertRow();
for (key in element) {
let cell = row.insertCell();
let text = document.createTextNode(element[key]);
cell.appendChild(text);
}
}
};
let table = document.querySelector("table");
let tableData = Object.keys(line_items.data[0]);
generateTableHead(table, tableData);
generateTable(table, line_items.data);
}
};
xmlHttp.send(JSON.stringify(body));
This is what I am trying to achieve
|-----------------------------------------|
| Count | Related Invoice | TEST1 | TEST2 |
|-------|-----------------|-------|-------|
| 1 | 11.0 | A | B |
|-------|-----------------|-------|-------|
| 2 | 11.0 | C | D |
|-----------------------------------------|
I need to accomplish 3 things:
#1 Rename "6", "69 and "70" to the corresponding fields.label ( "Related Invoice", "TEST1" and "TEST2" ).
#2 Take the value of the objects nested under the objects shown above in #1 ( 11.0, "A", "B", ... ) and set them as the value of the objects shown in #1 above. This would, for example, make 6 (Related Invoice) the key and 11.0 the value.
#3 I ultimately want to display this in a table on a webpage. the html and css I can handle its the Javascript and JSON that I am not that great with.
If you need me to clarify anymore information please let me know.
To transform the data in the way you're looking, you'll need to loop over the data key in the object and create a new array based on the result of the loop.
A way to do this is with Array.prototype.map(). With this you can loop over each item in the array and return a new value.
In this map loop you are looping over each item in the data array. For each item you'll want to get the id and label from the fields array and use that array to create a new object. To create a new object within in a loop, you could use the Array.prototype.reduce() method.
So in this case you'll have a nested loop. The inner loop will loop over the fields array and uses the id to get the correct value from data array. It then returns an object with the label and the value set like you requested. The surrounding map method will then return a new array with objects. Tadaa, magic!
const response = {
"data": [{
"6": {
"value": 11.0
},
"69": {
"value": "A"
},
"70": {
"value": "B"
}
},
{
"6": {
"value": 11.0
},
"69": {
"value": "C"
},
"70": {
"value": "D"
}
}
],
"fields": [{
"id": 6,
"label": "Related Invoice",
"type": "numeric"
},
{
"id": 69,
"label": "TEST1",
"type": "text"
},
{
"id": 70,
"label": "TEST2",
"type": "text"
}
],
"metadata": {
"numFields": 3,
"numRecords": 2,
"skip": 0,
"totalRecords": 2
}
};
const transformResponseData = (response) => {
const { data, fields } = response;
// Return a new array with objects based on the values
// of the data and fields arrays.
const revivedData = data.map(entry =>
fields.reduce((object, { id, label }) => {
object[label] = entry[id].value;
return object;
}, {})
);
// Combine the original object with the new data key.
return {
...response,
data: revivedData
};
};
const createTable = ({ data, fields }) => {
const table = document.createElement('table');
const tHead = table.createTHead();
const tBody = table.createTBody();
const tHeadRow = tHead.insertRow();
// Create the counts cell manually.
const tHeadRowCountCell = document.createElement('th');
tHeadRowCountCell.textContent = 'Count';
tHeadRow.append(tHeadRowCountCell);
// Create a head for each label in the fields array.
for (const { label } of fields) {
const tHeadRowCell = document.createElement('th');
tHeadRowCell.textContent = label;
tHeadRow.append(tHeadRowCell);
}
// Output all the values of the new data array.
for (const [index, entry] of data.entries()) {
const tBodyRow = tBody.insertRow();
// Create a new array with the index and the
// values from the object.
const values = [
index + 1,
...Object.values(entry)
];
// Loop over the combined values array.
for (const [index, value] of values.entries()) {
const tBodyCell = tBodyRow.insertCell();
tBodyCell.textContent = index === 1 ?
value.toFixed(1) :
value;
}
}
return table;
};
const data = transformResponseData(response);
const table = createTable(data);
document.body.append(table);

If object value exists in array, add to existing object

I have a function component that generates a linear chart in react based on an array of objects.
The data I'm pulling from the API looks like this:
{
"_id": "604face09b305032586fe235",
"username": "Demo",
"description": "0",
"duration": 0,
"date": "2021-03-15T18:52:10.749Z",
"createdAt": "2021-03-15T18:52:16.684Z",
"updatedAt": "2021-03-15T18:52:16.684Z",
"__v": 0
}
My code to generate the chart data is:
// GET Exercises
useEffect(() => {
axios.get("http://localhost:5000/exercises")
.then((result) => {
setIsLoaded(true);
setItems(result.data);
console.log(result.data)
})
.catch((error) => {
setIsLoaded(true);
setError(error);
})
}, [])
// chartdata
useEffect(() => {
const chartArray = items.map((item) => {
const container = {};
container.label = item.username;
container.data = [[stripDate(item.date), item.duration]]
return container;
});
setXp(chartArray);
}, [items])
However, the API could return multiple objects with "username": "Demo", what I want is that if the username value already exists in my chart data I want to update that container.data array with another array.
Current outcome:
{
"label": "Demo",
"data": [[15, "2021-03-15"]]
},
{
"label": "Demo",
"data": [[45, "2021-03-17"]]
}
What I want to achieve:
{
"label": "Demo",
"data": [[15, "2021-03-15"], [45, "2021-03-17"]]
}
Any help or guidance is appreciated.
You need to group your data based on the username value using array#reduce in an object accumulator. For repeated username push new data in the array. Then get the values from this accumulator using Object.values().
useEffect(() => {
const chartArray = Object.values(items.reduce((r, o) => {
r[o.username] = r[o.username] || { label: o.username, data: []};
r[o.username].data.push([o.duration, stripDate(o.date)]);
return r;
},{}));
setXp(chartArray);
}, [items])

Loop through JSON array of objects and get the properties based on the matching IDs from objects

My target is if the id from digital_assets and products matches then get the value of URL fro digital_assets and ProductName from products object. I'm able to traverse through the object and get the values of digital_assets and products but need some help to compare these two objects based on IDs to get the value of URL and ProductName. Below is what I've done so far.
var data = [{
"digital_assets": [{
"id": "AA001",
"url": "https://via.placeholder.com/150"
},{
"id": "AA002",
"url": "https://via.placeholder.com/150"
}]
}, {
"products": [{
"id": ["BB001", "AA001"],
"ProductName": "PROD 485"
},{
"id": ["BB002", "AA002"],
"ProductName": "PROD 555"
}]
}
];
$.each(data, function () {
var data = this;
//console.log(data);
$.each(data.digital_assets, function () {
var dAssets = this,
id = dAssets['id'];
// console.log(id);
});
$.each(data.products, function () {
var proData = this,
prod_id = proData['id'];
// console.log(prod_id);
$.each(prod_id, function () {
var arr_id = this;
console.log(arr_id);
});
});
});
Do I need to create new arrays and push the values into the new arrays? Then concat() these array to one. ? Bit lost any help will be appreciated.
Here is one way you can do this via Array.reduce, Array.includes, Object.entries and Array.forEach:
var data = [{ "digital_assets": [{ "id": "AA001", "url": "https://via.placeholder.com/150" }, { "id": "AA002", "url": "https://via.placeholder.com/150" } ] }, { "products": [{ "id": ["BB001", "AA001"], "ProductName": "PROD 485" }, { "id": ["BB002", "AA002"], "ProductName": "PROD 555" } ] } ]
const result = data.reduce((r,c) => {
Object.entries(c).forEach(([k,v]) =>
k == 'digital_assets'
? v.forEach(({id, url}) => r[id] = ({ id, url }))
: v.forEach(x => Object.keys(r).forEach(k => x.id.includes(k)
? r[k].ProductName = x.ProductName
: null))
)
return r
}, {})
console.log(Object.values(result))
You can use Array.prototype.find, Array.prototype.includes and Array.prototype.map to achieve this very gracefully.
let data = [
{
"digital_assets": [
{
"id": "AA001",
"url": "https://via.placeholder.com/150"
},
{
"id": "AA002",
"url": "https://via.placeholder.com/150"
}
]
},
{
"products": [
{
"id": ["BB001", "AA001"],
"ProductName": "PROD 485"
},
{
"id": ["BB002","AA002"],
"ProductName": "PROD 555"
}
]
}
];
// Find the 'digital_assets' array
let assets = data.find(d => d['digital_assets'])['digital_assets'];
// Find the 'products' array
let products = data.find(d => d['products'])['products'];
// Return an array of composed asset objects
let details = assets.map(a => {
return {
id : a.id,
url : a.url
name : products.find(p => p.id.includes(a.id)).ProductName
};
});
console.log(details);
changed answer to fit your needs:
var data = [
{
"digital_assets": [
{
"id": "AA001",
"url": "https://via.placeholder.com/150"
},
{
"id": "AA002",
"url": "https://via.placeholder.com/150"
}
]
},
{
"products": [
{
"id": ["BB001", "AA001"],
"ProductName": "PROD 485"
},
{
"id": ["BB002","AA002"],
"ProductName": "PROD 555"
}
]
}
]
let matchingIds = [];
let data_assetsObject = data.find(element => {
return Object.keys(element).includes("digital_assets")
})
let productsObject = data.find(element => {
return Object.keys(element).includes("products")
})
data_assetsObject["digital_assets"].forEach(da => {
productsObject["products"].forEach(product => {
if (product.id.includes(da.id)){
matchingIds.push({
url: da.url,
productName: product.ProductName
})
}
})
})
console.log(matchingIds);
working fiddle: https://jsfiddle.net/z2ak1fvs/3/
Hope that helped. If you dont want to use a new array, you could also store the respective data within the element you are looping through.
Edit:
I think i know why i got downvoted. My example works by making data an object, not an array. changed the snippet to show this more clearly.
Why is data an array anyway? Is there any reason for this or can you just transform it to an object?
Edit nr2:
changed the code to meet the expectations, as i understood them according to your comments. it now uses your data structure and no matter whats in data, you can now search for the objects containing the digital_assets / products property.
cheers
https://jsfiddle.net/2b1zutvx/
using map.
var myobj = data[0].digital_assets.map(function(x) {
return {
id: x.id,
url: x.url,
ProductName: data[1].products.filter(f => f.id.indexOf(x.id) > -1).map(m => m.ProductName)
};
});

How to convert JSON object to array of String and number in JavaScript

I have the following response from a GET request to an API end point:
{
"success": true,
"data": {
"categories": [
{
"id": 1,
"label": "Default",
"url": "default",
"num_of_subs": 0
},
{
"id": 2,
"label": "Pet Grooming",
"url": "pet-grooming",
"num_of_subs": 2
},
],
}
}
Here is the fetch code:
const sendRegisterData = fetch('https://sg.cuzzey.com/api/listings/categories', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => response.text())
.then((responseText) => {
var response_final = JSON.parse(responseText);
if (response_final.success) {
this.setState({
categories: response_final.data.categories,
})
}
})
What I want to do, is to get the data from the categories and print them out as String and number (id and num_of_subs as number, label and url as String). Here is what I did:
const category_options = this.state.categories.map(category => {
return [<Text key={category.id} style={styles.smallTextBox}> {category.id} {'\n'}</Text>,
<Text key={category.label} style={styles.smallTextBox}> {category.label} {'\n'}</Text>,
<Text key={category.num_of_subs} style={styles.smallTextBox}> {category.num_of_subs} {'\n'}</Text>]
})
<ModalDropdown options={category_options} onSelect={(idx, label) => this.getCategory(idx, label)}>
<Text style={styles.smallTextBox}> {this.state.selected_category} </Text>
</ModalDropdown>
I used this code to get the value of id, label and num_of_subs:
getCategory = (idx, value) => {
this.setState({ category_id: value[0], selected_category: value[1], numSubs: value[2] })
}
When I print out category_id, selected_category and numSubs, the values are correct. However, they are object types and not String/number. I want to manipulate the values but I don't know how to convert them into String/number. I have tried using String(), Number(), parseInt(), JSON.parse(), toString() but none seems to work as they all result in "object" when I print out the typeof of the values.
Can I ask is there a way to solve this?
Your code is not working because you are not iterating through the array of objects. You are simply returning the object of arrays. This is why you cannot extract the value
var items = [{
"success": true,
"data": {
"categories": [{
"id": 1,
"label": "Default",
"url": "default",
"num_of_subs": 0
},
{
"id": 2,
"label": "Pet Grooming",
"url": "pet-grooming",
"num_of_subs": 2
},
],
}
}]
setState = [];
var catagories = items.map(x => x.data.categories);
catagories = catagories.reduceRight(x => x.cantact([]));
getCategory = (values) => {
for (let value of values) {
setState.push({
category_id: value.id,
selected_category: value.label,
numSubs: value.num_of_subs
})
}
}
var output = getCategory(catagories)
var results = setState.filter(x => x.numSubs > 0)
console.log(results);

Javascript how to return array from map

I have the following Json
var myjson = [{
"files": [
{
"domain": "d",
"units": [
{
"key": "key1",
"type": "2"
},
{
"key": "key2",
"type": "2"
},
{
"key": "key3",
"type": "2"
}]
},
{
"domain": "d1",
"units": [
{
"key": "key11",
"type": "2"
},
{
"key": "key12",
"type": "2"
},
{
"key": "key13",
"type": "2"
}]
}]
},
{
"files": [
{
"domain": "d",
"units": [
{
......
I want to create an new array from this Json array. The length of array will be the number of "units" in this Json object.
So I need to extract "units" and add some data from parent objects.
units: [{
domain: "",
type: "",
key: ""
}, {
domain: "",
type: "",
key: ""
},
{
domain: "",
type: "",
key: ""
}
....
];
I guess i can probably do something like this:
var res = [];
myjson.forEach(function(row) {
row.files.forEach(function(tfile) {
tfile.units.forEach(function(unit) {
var testEntity = {
domain: tfile.domain,
type : unit.type,
key: unit.key
};
res.push(testEntity);
});
});
});
But it is difficult to read and looks not so good. I was thinking to do something like :
var RESULT = myjson.map(function(row) {
return row.files.map(function(tfile) {
return tfile.units.map(function(unit) {
return {
domain: tfile.domain,
type : unit.type,
key: unit.key
};
});
});
});
But This doesn't work and looks not better . Is there any way to do so it works, maybe in more declarative way. hoped Ramda.js could help.
It there any good approach in general to get data from any Nested json in readable way?
Implementing something like:
nestedjson.findAllOnLastlevel(function(item){
return {
key : item.key,
type: type.key,
domain : item.parent.domain}
});
Or somehow flatten this json so all properties from all parents object are moved to leafs children. myjson.flatten("files.units")
jsbin http://jsbin.com/hiqatutino/edit?css,js,console
Many thanks
The function you can use here is Ramda's R.chain function rather than R.map. You can think of R.chain as a way of mapping over a list with a function that returns another list and then flattens the resulting list of lists together.
// get a list of all files
const listOfFiles =
R.chain(R.prop('files'), myjson)
// a function that we can use to add the domain to each unit
const unitsWithDomain =
(domain, units) => R.map(R.assoc('domain', domain), units)
// take the list of files and add the domain to each of its units
const result =
R.chain(file => unitsWithDomain(file.domain, file.units), listOfFiles)
If you wanted to take it a step further then you could also use R.pipeK which helps with composing functions together which behave like R.chain between each of the given functions.
// this creates a function that accepts the `myjson` list
// then passes the list of files to the second function
// returning the list of units for each file with the domain attached
const process = pipeK(prop('files'),
f => map(assoc('domain', f.domain), f.units))
// giving the `myjson` object produces the same result as above
process(myjson)
Pure JS is very sufficient to produce the result in simple one liners. I wouldn't touch any library just for this job. I have two ways to do it here. First one is a chain of reduce.reduce.map and second one is a chain of reduce.map.map. Here is the code;
var myjson = [{"files":[{"domain":"d","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"d1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]},{"files":[{"domain":"e","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"e1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]}],
units = myjson.reduce((p,c) => c.files.reduce((f,s) => f.concat(s.units.map(e => (e.domain = s.domain,e))) ,p) ,[]);
units2 = myjson.reduce((p,c) => p.concat(...c.files.map(f => f.units.map(e => (e.domain = f.domain,e)))) ,[]);
console.log(units);
console.log(units2);
For ES5 compatibility i would suggest the reduce.reduce.map chain since there is no need for a spread operator. And replace the arrow functions with their conventional counterparts like the one below;
var myjson = [{"files":[{"domain":"d","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"d1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]},{"files":[{"domain":"e","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"e1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]}],
units = myjson.reduce(function(p,c) {
return c.files.reduce(function(f,s) {
return f.concat(s.units.map(function(e){
e.domain = s.domain;
return e;
}));
},p);
},[]);
console.log(units);
Something like this should work. .reduce is a good one for these kind of situations.
const allUnits = myjson.reduce((acc, anonObj) => {
const units = anonObj.files.map(fileObj => {
return fileObj.units.map(unit => {
return {...unit, domain: fileObj.domain})
})
return [...acc, ...units]
}, [])
Note that this relies on both array spreading and object spreading, which are ES6 features not supported by every platform.
If you can't use ES6, here is an ES5 implementation. Not as pretty, but does the same thing:
var allUnits = myjson.reduce(function (acc, anonObj) {
const units = anonObj.files.map(function(fileObj) {
// for each fileObject, return an array of processed unit objects
// with domain property added from fileObj
return fileObj.units.map(function(unit) {
return {
key: unit.key,
type: unit.type,
domain: fileObj.domain
}
})
})
// for each file array, add unit objects from that array to accumulator array
return acc.concat(units)
}, [])
Try this
var myjson = [{
"files": [{
"domain": "d",
"units": [{
"key": "key1",
"type": "2"
}, {
"key": "key2",
"type": "2"
}, {
"key": "key3",
"type": "2"
}]
},
{
"domain": "d1",
"units": [{
"key": "key11",
"type": "2"
}, {
"key": "key12",
"type": "2"
}, {
"key": "key13",
"type": "2"
}]
}
]
}];
//first filter out properties exluding units
var result = [];
myjson.forEach(function(obj){
obj.files.forEach(function(obj2){
result = result.concat(obj2.units.map(function(unit){
unit.domain = obj2.domain;
return unit;
}));
});
});
console.log(result);

Categories

Resources