recursively turn massive JSON to a html list - javascript

I have been trying for a week to turn a HUGE javascript object to an unordered list. the function I have right now doesnt right. it returns only the first layer. the rest it doesnt. freezing of the browser is also a problem which my solution is to use setTimeout(function,0);
JSON format:
{
"name": "",
"value": { //JSON always start with these 2. content is inside this value key.
"randomname": {
"type": "list",
"value": {
"type": "int", //another type and value to that is in the array
"value": [26, 32, 25]
}
},
"randomname": {
"type": "int",
"value": 5
},
"randomname": {
"type": "string",
"value": "string or something"
},
"randomname": {
"type": "compound", //compound types can be inside compound types can be inside compount types etc.
"value": {
"randomname": {
"type": "int",
"value": 6
},
"randomname": {
"type": "int",
"value": 6
}
}
},
"randomname": {
"type": "long",
"value": [0.0345034, 4.345345]
}
}
The code I tried to make:
function readObject(el, data, boo) {
for(var ind in data) {
el.innerHTML += '<li>';
if (boo) {
el.innerHTML += ind + ' - ';
}
console.log("another loop");
switch (data[ind].type) {
case "compound":
el.innerHTML += data[ind].type + ' - ';
setTimeout(function (){ readObject(el,data[ind].value,false); el.innerHTML += '</li>';},0);
break;
case "string":
el.innerHTML += data[ind].type + ' - ';
el.innerHTML += data[ind].value;
callback();
break;
case "int":
el.innerHTML += data[ind].type + ' - ';
el.innerHTML += data[ind].value;
callback();
break;
case "long":
el.innerHTML += data[ind].type + ' - ';
el.innerHTML += data[ind].value[0] + ' - ' + data[ind].value[1];
callback();
break;
case "list":
el.innerHTML += data[ind].type + ' - ';
el.innerHTML += data[ind].value.type + ' - ';
setTimeout(function (){ readObject(el,listToObject(data[ind].value),false);el.innerHTML += '</li>'; },0);
break;
}
function callback() {
el.innerHTML += '</li>';
}
}
}
(All other types will be the same of string)

Manipulating the DOM is slow.
But there is also another issue with your code: you use setTimeout, and in the callbacks you provide to it, you reference a variable ind that will have changed by the time the callback is executed, leading to undesired results if any.
Your code does not produce any ul tags, only li tags. NB: You did not provide the definition of the function listToObject, but I assume it is not problematic (although arrays are objects).
Here is an implementation that will also deal well with cases where you have a list of less trivial sub types, such as "compound" -- I added an example at the end of the sample data:
function objectToHtml(data, defaultType) {
var html = '';
for (var ind in data) {
var item = data[ind],
type = defaultType || item.type,
value = defaultType ? item : item.value;
html +=
'<li>\n' +
(defaultType ? '' : ind + ' - ' + type + ' - ') +
(type === "compound" ? objectToHtml(value)
: type === "list" ? value.type + ' - '
+ objectToHtml(value.value, value.type)
: type === "long" ? value.join(' - ')
: value) +
'</li>\n';
}
return '<ul>\n' + html + '</ul>\n';
}
// Sample data
var data = {
"name": "",
"value": {
"randomname1": {
"type": "list",
"value": {
"type": "int",
"value": [26, 32, 25]
}
},
"randomname2": {
"type": "int",
"value": 5
},
"randomname3": {
"type": "string",
"value": "string or something"
},
"randomname4": {
"type": "compound",
"value": {
"randomname5": {
"type": "int",
"value": 6
},
"randomname6": {
"type": "int",
"value": 6
}
}
},
"randomname7": {
"type": "long",
"value": [0.0345034, 4.345345]
},
"randomname8": {
"type": "list",
"value": {
"type": "compound",
"value": [{
"randomnameA": {
"type": "int",
"value": 7
},
"randomnameB": {
"type": "int",
"value": 8
}
}, {
"randomnameC": {
"type": "int",
"value": 9
},
"randomnameD": {
"type": "int",
"value": 10
}
}]
}
}
}
};
tree.innerHTML = objectToHtml(data.value);
<div id="tree"></div>

I'm not certain I've exactly matched the HTML structure you're trying to create, but this should be close enough for you to modify to fit your needs.
The fastest approach is to construct a single string and drop it into the DOM, instead of doing incremental DOM operations:
var convert = function(input) {
var out = '';
out += '<ul>';
for (k in input) {
var obj = input[k];
out += '<li>' + k + ' - ' + obj.type + ' - ';
switch(obj.type) {
case "compound":
// recurse:
out += convert(obj.value); break;
case "long":
out += obj.value.join(' - '); break;
case "list":
out += obj.value.type + ' - ';
out += obj.value.value.join(' - '); break;
default:
// looks like all the other cases are identical:
out += obj.value;
}
out += '</li>';
}
out += '</ul>';
return out;
}
var data = {
"name": "",
"value": {
"randomname1": {
"type": "list",
"value": {
"type": "int",
"value": [26, 32, 25]
}
},
"randomname2": {
"type": "int",
"value": 5
},
"randomname3": {
"type": "string",
"value": "string or something"
},
"randomname4": {
"type": "compound",
"value": {
"randomname5": {
"type": "int",
"value": 6
},
"randomname6": {
"type": "int",
"value": 6
}
}
},
"randomname7": {
"type": "long",
"value": [0.0345034, 4.345345]
}
}
};
document.getElementById('output').innerHTML = convert(data.value);
<div id="output"></div>
Constructing this as an isolated document fragment rather than a single HTML string is, in this case, about 25% slower: https://jsperf.com/tree-test-fragment-vs-string/1
But with a little tweaking that slower method has the advantage of allowing you to use your setTimeout trick to defer each level of recursion, so the browser (one hopes) won't freeze up while trying to draw very large trees.
Here the recursion is handled in a separate function, so each setTimeout will be working with the correct data and DOM nodes (trying to do it all in the same function means that the iteration will have overwritten the variables with new data by the time the setTimeout fires -- this was part of why your original code wasn't recursing properly.) I added some deeper compound types to the data to demonstrate that this is working:
var convert = function(input) {
var fragment = document.createDocumentFragment();
var ul = document.createElement('ul');
fragment.appendChild(ul);
for (k in input) {
var obj = input[k];
var li = document.createElement('li');
var txt = k + ' - ' + obj.type + ' - ';
if (obj.type === 'compound') {
li.innerHTML = txt;
recurse(li, obj.value); // acts as a closure for these vars
} else {
switch (obj.type) {
case "long":
txt += obj.value.join(' - ');
break;
case "list":
txt += obj.value.type + ' - ';
txt += obj.value.value.join(' - ');
break;
default:
txt += obj.value;
}
li.innerHTML = txt;
}
ul.appendChild(li);
}
return fragment;
}
var recurse = function(li, d) {
window.setTimeout(function() {
li.appendChild(convert(d));
}, 1);
}
var data = {
"name": "",
"value": {
"randomname1": {
"type": "list",
"value": {
"type": "int",
"value": [26, 32, 25]
}
},
"cptest1": {
"type": "compound",
"value": {
"x": {
"type": "int",
"value": 2
},
"cptest2": {
"type": "compound",
"value": {
"y": {
"type": "int",
"value": 2
},
"z": {
"type": "int",
"value": 3
}
}
},
}
},
"randomname3": {
"type": "string",
"value": "string or something"
},
"randomname4": {
"type": "compound",
"value": {
"randomname5": {
"type": "int",
"value": 6
},
"randomname6": {
"type": "int",
"value": 6
}
}
},
"randomname7": {
"type": "long",
"value": [0.0345034, 4.345345]
}
}
};
document.getElementById('output').appendChild(convert(data.value));
<div id="output"></div>

Related

How to append an input inside each div that created from Json

i have a json and i create a simple dynamic form based on my json file . there are labels and each lables are exist in divs seperately.but there is a problem in this code that this code does not show my inputs. how to appends them inside each divs below each lable?
this code should show input but does not work.
here is my code :
document.body.onload = addElement;
function addElement() {
var schema = [{
"queestion": "Name",
"type": "128",
"attrs": [{
"attr": {
"name": "class",
"value": "nofilling2 engword"
}
},
{
"attr": {
"name": "id",
"value": "name"
}
},
{
"attr": {
"name": "type",
"value": "text"
}
},
{
"attr": {
"name": "placeholder",
"value": "Name"
}
},
{
"attr": {
"name": "name",
"value": "_root.passengerinfo__1.passengerinfo.fullname.firstname"
}
}
]
},
{
"queestion": "Family",
"type": "128",
"attrs": [{
"attr": {
"name": "class",
"value": "nofilling2 engword"
}
},
{
"attr": {
"name": "id",
"value": "family"
}
},
{
"attr": {
"name": "type",
"value": "text"
}
},
{
"attr": {
"name": "placeholder",
"value": "Family"
}
},
{
"attr": {
"name": "name",
"value": "_root.passengerinfo__1.passengerinfo.fullname.lastname"
}
}
]
}
]
for (var i = 0; i < schema.length; i++) {
var type = schema[i].type;
if (type == 128) {
var titleinput = schema[i].queestion;
var newDiv = document.createElement("div");
newDiv.className = 'c-infoo';
var newContent = document.createElement('label');
newContent.className = 'abs-tlt';
newDiv.appendChild(newContent);
newContent.innerHTML = titleinput + " : ";
document.getElementById('tblreserve').appendChild(newDiv);
var string = "<input ";
for (var y = 0; y < schema[i].attrs.length; y++) {
string += schema[i].attrs[y].attr.name + '="' + schema[i].attrs[y].attr.value + '" '
}
string += ">";
newDiv.appendChild = string;
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<div id="tblreserve"></div>
In your last line you wrote newDiv.appendChild=string;.
But stringas its name says is a String and can't be appended as an child.
You can use your string by writing newDiv.innerHTML+=string; instead.
PS: Please fix your indentation style. It doesn't look very nice...

Create JSON object with same keys for API call

I'm trying to create a JSON object for an API call which has the following format:
....
"Key": "Value",
"Package": {
"Dimensions": {
"UnitOfMeasurement": {
"Code": "IN",
"Description": "inches"
},
"Length": "20",
"Width": "25",
"Height": "30"
},
"PackageWeight": {
"UnitOfMeasurement": {
"Code": "Lbs",
"Description": "pounds"
},
"Weight": "80"
}
},
"Package": {
"Dimensions": {
"UnitOfMeasurement": {
"Code": "IN",
"Description": "inches"
},
"Length": "15",
"Width": "24",
"Height": "27"
},
"PackageWeight": {
"UnitOfMeasurement": {
"Code": "Lbs",
"Description": "pounds"
},
"Weight": "50"
}
},
"Key": "Value",
....
I should add as many "Package" objects as needed. However, I've tried doing this in many different ways but every time that I parse the variable to be used the first objects get overwritten and I end up with only the last object.
This is what I'm trying at the moment, still with no luck:
var lineItems = '{';
for (var i=0;i<inputObject.packages.length;i++) {
lineItems += '"Package": {"PackagingType": {"Code": "02","Description": "Rate"},"Dimensions": {"UnitOfMeasurement": {"Code": "IN","Description": "inches"},"Length": ' + inputObject.packages[i][0].toString() + ',"Width": ' + inputObject.packages[i][1].toString() + ',"Height": ' + inputObject.packages[i][2].toString() + '},"PackageWeight": {"UnitOfMeasurement": {"Code": "Lbs","Description": "pounds"},"Weight": ' + inputObject.packages[i][3].toString() + '}}';
if (i !== inputObject.packages.length-1) {
lineItems += ',';
}
}
lineItems += '}';
lineItems = JSON.parse(lineItems);
How about numbering your packages, ie:
for (var i=0;i<inputObject.packages.length;i++) {
lineItems+='"Package" + i : { ... }'
}
edit: to get required result (as an array - because it's not JSON), here's an example:
var a=[];
var b={"package": {"c":100,"d":200,"e":300}}
var c={"package": {"c":800,"d":700,"e":600}}
a.push(b);
a.push(c);
console.log(a);

how to navigate through complex JSON dynamically in JavaScript

I'm trying to navigate through a complex nested JSON, however my attempt didn't get me too far as it keeps returning me the last index JSON.
This is how my Objects looks like, and trying to navigate through it and getting other objs/schemas that are in $ref.
Raw JSON
{
"type": "object",
"properties": {
"Id": {
"format": "int32",
"type": "integer"
},
"Status": {
"enum": [
"Preparing",
"AwaitingCompletion",
"Cancelled",
"Completed"
],
"type": "string"
},
"ExternalReference": {
"type": "string"
},
"Customer": {
"$ref": "#/definitions/Customer"
},
"OrderLineGroups": {
"type": "array",
"items": {
"$ref": "#/definitions/OrderLineGroup"
}
},
"Promotions": {
"type": "array",
"items": {
"$ref": "#/definitions/PromotionSummary"
}
},
"OriginatingSite": {
"type": "object",
"properties": {
"Id": {
"format": "int32",
"type": "integer"
},
"PropertyCode": {
"type": "string"
},
"StoreCode": {
"type": "string"
}
}
},
"CustomData": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
In my code I have done for() and hasOwnProperty(), however my problem is that it doesn't give me all the JSON back even the condition is met (e.g if there's no type property), it only gives the last index or object that doesn't have type property. Also doesn't return me any of the objects if type property is array.
// Get property of #/definitions/obj
let prop = apiDefinition[splitResponse.split('/')[2]].properties;
console.log([prop])
var s = [apiDefinition[splitResponse.split('/')[2]].properties];
// Transform JS Object of #/definitions/Obj to JSON
var parentJSON = JSON.stringify(apiDefinition[splitResponse.split('/')[2]]);
for (var x in prop) {
if (prop.hasOwnProperty(x)) {
if (prop[x].type && prop[x].type === 'array') {
console.log('All type Array >> ', x);
let objKeyProp = apiDefinition[prop[x].items.$ref.split('/')[2]];
let objJsonStringified = JSON.stringify(objKeyProp);
let refString = '{"$ref"'+':' + '"' + prop[x].items.$ref + '"}';
this.compiledJson = JSON.parse(parentJSON.replace(refString, objJsonStringified));
} else if (!prop[x].type) {
console.log('all arrays >> ', x)
let objKeyProp = apiDefinition[prop[x].$ref.split('/')[2]];
let objJsonStringified = JSON.stringify(objKeyProp);
let refString = '{"$ref"'+':' + '"' + prop[x].$ref + '"}';
this.compiledJson = JSON.parse(parentJSON.replace(refString, objJsonStringified));
}
}
}

Reading JSON data and looping through it

I have 3 files
index.html
data.json
app.js
on my WAMP server. I wish to loop through the JSON data and update contents of the HTML file.
These are the contents of my files:
data.json
{
"user": [{
"goal": "HTML essential training",
"type": "Beginner",
"date": "20/07/2016"
}, {
"goal": "CSS essential training",
"type": "Beginner",
"date": "30/07/2016"
}, {
"goal": "CSS core concepts",
"type": "Intermediate",
"date": "10/08/2016"
}, {
"goal": "Javascript essential training",
"type": "Beginner",
"date": "20/08/2016"
}, {
"goal": "Object Oriented JS",
"type": "Advanced",
"date": "30/08/2016"
}]
}
app.js
var request = new XMLHttpRequest();
request.open('GET', 'data.json');
request.onreadystatechange = function() {
if ((request.readyState === 4) && (request.status === 200)) {
var items = JSON.parse(request.responseText);
console.log(items);
for (var key in items) {
console.log(key);
var output = "<tr><td><input type='checkbox' name='record'></td><td>" + items[key].goal + "</td><td>" + items[key].type + "</td><td>" + items[key].date + "</td></tr>";
console.log(output);
$("table tbody").append(output);
output = '';
}
}
}
request.send();
When i run this code, one row is created with all the values set to undefined. I think there's a problem with my looping logic.Please help me.
You are running into this since the object returned is a single key: value pair.
You need to access the user property which is an array.
your code
var items=JSON.parse(request.responseText);
The value you get after parsing the json string is the javascript object with key : 'user' and value : which is an array
Supposed to be
var data =JSON.parse(request.responseText);
var items = data.user; <-- this is an array of objects
// iterate using a for loop, since it is not an object technically
for(var i=0; i < items.length; i++) {
Take a look at the structure of your object:
{
"user": [
{
"goal": "HTML essential training",
"type": "Beginner",
"date": "20\/07\/2016"
},
{
"goal": "CSS essential training",
"type": "Beginner",
"date": "30\/07\/2016"
},
{
"goal": "CSS core concepts",
"type": "Intermediate",
"date": "10\/08\/2016"
},
{
"goal": "Javascript essential training",
"type": "Beginner",
"date": "20\/08\/2016"
},
{
"goal": "Object Oriented JS",
"type": "Advanced",
"date": "30\/08\/2016"
}
]
}
There's one key, with the value being an array.
You need a loop like this:
for (var key in items){if (items.hasOwnProperty(key)) {
for (var i in items[key]) {if (items[key].hasOwnProperty(i)){
console.log(items[key][i]);
var output="<tr><td><input type='checkbox' name='record'></td><td>" + items[key][i].goal + "</td><td>" + items[key][i].type + "</td><td>" + items[key][i].date + "</td></tr>";
console.log(output);
$("table tbody").append(output);
output='';
}}
}}

Access object var in object [duplicate]

This question already has answers here:
Self-references in object literals / initializers
(30 answers)
Closed 6 years ago.
I have an object like this...
"data": {
"1": {
"id": 32,
"name": "E",
"link": "",
},
"2": {
"id": 33,
"name": "EC",
"link": "",
},
}
how can I set each "link" value using the vars above ? for example I would like to do some thing like
"data": {
"1": {
"id": 32,
"name": "E",
"link": "id="+this.id+" and name="+this.name,
},
"2": {
"id": 33,
"name": "EC",
"link": "id="+this.id+" and name="+this.name,
},
To make the object like this...
"data": {
"1": {
"id": 32,
"name": "E",
"link": "id=32 and name=E",
},
"2": {
"id": 33,
"name": "EC",
"link": "id=33 and name=EC",
},
I suggest to iterate over the properties and change the wanted items.
var object = { data: { "1": { "id": 32, "name": "E", "link": "", }, "2": { "id": 33, "name": "EC", "link": "", } } };
Object.keys(object.data).forEach(function (k) {
object.data[k].link = 'id=' + object.data[k].id + ' and name=' + object.data[k].name;
});
document.write('<pre>' + JSON.stringify(object, 0, 4) + '</pre>');
I typically make it a function:
"data": {
"1": {
"id": 32,
"name": "E",
"get_link": function() { return "id="+this.id+" and name="+this.name }
}
}
Then you access it like whatever.get_link()
There are two ways to go about solving this.
You can create a post-processor to process placeholder values. I made a generic pre-processor which should dynamically search for placeholders for all string properties.
var linkStore = {
"data": {
"1": {
"id": 32,
"name": "E",
"link": "id={{id}} and name={{name}}"
},
"2": {
"id": 33,
"name": "EC",
"link": "id={{id}} and name={{name}}"
}
}
};
function postProcess(data, root) {
var records = root != null ? data[root] : data;
Object.keys(records).forEach(function(recordId) {
var record = records[recordId];
Object.keys(record).forEach(function(property) {
if (typeof record[property] === 'string') {
record[property] = record[property].replace(/{{(\w+)}}/g, function(match, value) {
return record[value];
});
}
});
});
return data;
}
var result = postProcess(linkStore, 'data');
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, 2) + '</pre>';
Or if you don't want to modify the original data. You can add a property map to be applied to each of the records.
var linkStore = {
"data": {
"1": {
"id": 32,
"name": "E",
"link": ""
},
"2": {
"id": 33,
"name": "EC",
"link": ""
}
}
};
function postProcess(data, root, propertyMap) {
var records = root != null ? data[root] : data;
Object.keys(records).forEach(function(recordId) {
var record = records[recordId];
Object.keys(propertyMap).forEach(function(property) {
record[property] = propertyMap[property].call(record, record[property]);
});
});
return data;
}
var result = postProcess(linkStore, 'data', {
link : function(oldValue) {
return 'id=' + this.id + ' and name=' + this.name
}
});
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, 2) + '</pre>';

Categories