how to print a javascript object's elements - javascript

i am new to javascript and i currently have an object printed to console when i use the following code:
clickEvents: {
click:function(target) {
console.log(target);
}
}
when i view console i can see the following object:
i am banging my head against a wall to write code that takes the object and prints it to a div using the .append() method. i am extermely new to working with javascript objects, and would appreciate any help trying to tease out an object and/or print the object data.
is events the object name? would i tease out the eventDate using something like events->eventDate?

I've made this over ~15 minutes so it's imperfect; there are types and edge cases surely unaccounted for and the design of the function could be better - not to mention that performing all of this as a giant string and then setting that as HTML is likely bad practice (I'm used to React now, ha!). Regardless, this will iterate over any array or object you pass to it and print it all in a big <ul> recursively.
const targetEl = document.querySelector('.js-target')
if (!targetEl) return
// Small helper functions
const isObj = data => typeof data === 'object' && !Array.isArray(data) && data !== null
const isArr = data => Array.isArray(data)
const dataToHTML = (data, noNode = false) => {
if (isObj(data)) {
const accumulator = Object.entries(data).reduce((acc, set) => acc + `<li><strong>${set[0]}</strong>: ${dataToHTML(set[1], true)}</li>`, '')
return `<ul>${accumulator}</ul>`
}
else if (isArr(data)) {
const accumulator = data.reduce((acc, item) => acc + dataToHTML(item), '')
return `<ul>${accumulator}</ul>`
}
else return noNode ? data : `<li>${data}</li>`
}
const logHTML = dataToHTML(exampleData)
targetEl.innerHTML = logHTML
Assuming that your data/variable is named exampleData.
Any questions pop them in the comments :-)

I'm not sure if you have a div that you want to append to already, but you would do something like this ->
document.getElementById("toBeAppendedTo").innerHTML = target.events[0].eventDate; where toBeAppendedTo is the id of the div you're trying to add this text to.
append() is a jquery function, not a javascript function.
That won't have any formatting and will just be the string value 07-28-2017 in a div.

Related

Remove a URL search parameter when there is duplicate names?

I am trying to manipulate my URL using URLSearchParams. However URLSearchParams.delete() expects the name of the param. If I have params with the same name, (from what I've tested in chrome) It will delete all params with that name. Is there a way to delete by both name and value?
My query looks something like this:
?color[]=Black&color[]=Green&material[]=Steel
So when I call .delete("color[]") it will remove both color[]= params, but what if I want to only remove a specific one?
The reason for the duplicate names is the backend (PHP) is leveraging this functionallity to auto parse the parameters into arrays...which requires the syntax above.
Big picture is- I'm trying to add/remove "filters" from this array-to-be. Also, some filter categories could have matching values so I don't want remove by value either. I am open to considering an entirely new approach...just trying to do it in the least hacky way.
-- Edit --
For any Laravel users, I recommend not using the index-less syntax. Just use color[0]=, color[1]= etc. I didn't realize laravel supports both syntaxes.
To remove a specific key/value pair, loop over the entries, filter out the unwanted one(s) and create a new URLSearchParams:
function deleteParamsEntry(params, key, value) {
const newEntries = Array.from(params.entries()).filter(
([k, v]) => !(k === key && v === value)
);
return new URLSearchParams(newEntries);
}
const query = "?color[]=Black&color[]=Green&material[]=Steel";
const params = new URLSearchParams(query);
const newParams = deleteParamsEntry(params, "color[]", "Green");
console.log(newParams.toString());
Try this approach:
const deleteURLParamsByNameAndValue = (urlString, paramName, paramValue) => {
const url = new URL(urlString)
const params = url.searchParams
const newParamArray = []
for (var kvPair of params.entries()) {
const k = kvPair[0]
const v = kvPair[1]
if (k!==paramName || v!==paramValue) {
newParamArray.push(kvPair)
}
}
const newSearch = new URLSearchParams(newParamArray)
return decodeURI(`${url.origin}${url.pathname}?${newSearch}`)
}
const urlString = 'https://example.com/path1/path2?color[]=Black&color[]=Green&material[]=Steel'
deleteURLParamsByNameAndValue(urlString,'color[]','Black')
// returns 'https://example.com/path1/path2?color[]=Green&material[]=Steel'

How can I map a concatenated value for this state, without it being passed as a string?

I'm new to Javascript.
I'm building this drumpad and I want to be able to switch between different soundpacks.
The sounds are imported from a separate file and the state is written like this:
import * as Sample from '../audiofiles/soundfiles'
const drumpadData = [
{
// other stuff
soundfile: Sample.sound1a
},
If I want to load a different soundpack, I have to change the state so the last letter (a,b,c) gets changed, so instead of Sample.sound1a, it would have to be Sample.sound1b. this is the function i wrote (on App.js):
changeSamples(id) {
let choice = document.getElementById("select-samplepack").value
this.setState(prevState => {
const updatedData = prevState.data.map(item => {
let newSoundfile = "Sample.sound" + item.id + choice
item.soundfile = newSoundfile
return item
})
return {
data: updatedData
}
})
}
It works, as in the value gets changed, but instead of react interpreting that and finding the correct import, the value of soundfile just stays as a string like "Sample.soundb1", so I get a load of media resource errors.
https://aquiab.github.io/drumpad/ heres the website, you can check the console to see the error, you have to load a different soundpack to reproduce the error.
https://github.com/aquiab/drumpad and here are the files:
I've thought of some ways of cheesing it, but I want the code to stay as clean as I can make it be.
Well that's because it is in fact a string. When you do:
"Sample.Sound" + item.id + choice you are doing type coersion. In JavaScript, that means you are converting the value of all data-types so that they share a common one. In this case your output resolves into a string. This will not be effective in finding the right sound in your dictionary.
Instead, what you need is bracket notation: Object[property]
Within the brackets we can define logic to identify the designated key belonging to the Object.
For example: Sample["sound" + item.id + choice] would evaluate to Sample["sound1b"] which is the same as Sample.sound1b
changeSamples(id) {
let choice = document.getElementById("select-samplepack").value
this.setState(prevState => {
const updatedData = prevState.data.map(item => {
item.soundfile = Sample["sound" + item.id + choice]
return item
})
return {
data: updatedData
}
})
}
I can think of two approaches here.
import Sample in App.jsx. and update sound file.
import * as Sample from '../audiofiles/soundfiles'
changeSamples(id) {
let choice = document.getElementById("select-samplepack").value
this.setState(prevState => {
const updatedData = prevState.data.map(item => {
let newSoundfile = Sample[`sound${item.id}${choice}`]
item.soundfile = newSoundfile
return item
})
return {
data: updatedData
}
})
}
You should save mapping of files in other object and update mapping and use mapping in drumpadData soundfile key.
Your problem comes from this line :
let newSoundfile = "Sample.sound" + item.id + choice
Here you are concatening your values item.id and choice with a string, so the result is a string and Sample is not interpreted as your imported object.
What you need is to wright something like
const sampleItem = "sound" + item.id + choice
let newSoundfile = Sample[sampleItem]
When you access an object property with the notation myObject[something], what's inside the bracket get interpreted. So in my example sample (which is a string because I concatenated a string "sound" with the variables) will be replaced with its string value (ex: "sound1a"), and newSoundFile will have as value the result of Sample["sound1a"].
I hope it make sens.

Comparing 2 Json Object using javascript or underscore

PS: I have already searched the forums and have seen the relevant posts for this wherein the same post exists but I am not able to resolve my issue with those solutions.
I have 2 json objects
var json1 = [{uid:"111", addrs:"abc", tab:"tab1"},{uid:"222", addrs:"def", tab:"tab2"}];
var json2 = [{id:"tab1"},{id:"new"}];
I want to compare both these and check if the id element in json2 is present in json1 by comparing to its tab key. If not then set some boolean to false. ie by comparing id:"tab1" in json2 to tab:"tab1 in json1 .
I tried using below solutions as suggested by various posts:
var o1 = json1;
var o2 = json2;
var set= false;
for (var p in o1) {
if (o1.hasOwnProperty(p)) {
if (o1[p].tab!== o2[p].id) {
set= true;
}
}
}
for (var p in o2) {
if (o2.hasOwnProperty(p)) {
if (o1[p].tab!== o2[p].id) {
set= true;
}
}
}
Also tried with underscore as:
_.each(json1, function(one) {
_.each(json2, function(two) {
if (one.tab!== two.id) {
set= true;
}
});
});
Both of them fail for some test case or other.
Can anyone tell any other better method or outline the issues above.
Don't call them JSON because they are JavaScript arrays. Read What is JSON.
To solve the problem, you may loop over second array and then in the iteration check if none of the objects in the first array matched the criteria. If so, set the result to true.
const obj1 = [{uid:"111", addrs:"abc", tab:"tab1"},{uid:"222",addrs:"def", tab:"tab2"}];
const obj2 = [{id:"tab1"},{id:"new"}];
let result = false;
for (let {id} of obj2) {
if (!obj1.some(i => i.tab === id)) {
result = true;
break;
}
}
console.log(result);
Unfortunately, searching the forums and reading the relevant posts is not going to replace THINKING. Step away from your computer, and write down, on a piece of paper, exactly what the problem is and how you plan to solve it. For example:
Calculate for each object in an array whether some object in another array has a tab property whose value is the same as the first object's id property.
There are many ways to do this. The first way involves using array functions like map (corresponding to the "calculate for each" in the question, and some (corresponding to the "some" in the question). To make it easier, and try to avoid confusing ourselves, we'll do it step by step.
function calculateMatch(obj2) {
return obj2.map(doesSomeElementInObj1Match);
}
That's it. Your program is finished. You don't even need to test it, because it's obviously right.
But wait. How are you supposed to know about these array functions like map and some? By reading the documentation. No one help you with that. You have to do it yourself. You have to do it in advance as part of your learning process. You can't do it at the moment you need it, because you won't know what you don't know!
If it's easier for you to understand, and you're just getting started with functions, you may want to write this as
obj2.map(obj1Element => doesSomeElementInObj1Match(obj1Element))
or, if you're still not up to speed on arrow functions, then
obj2.map(function(obj1Element) { return doesSomeElementInObj1Match(obj1Element); })
The only thing left to do is to write doesSomeElementInObj2Match. For testing purposes, we can make one that always returns true:
function doesSomeElementInObj2Match() { return true; }
But eventually we will have to write it. Remember the part of our English description of the problem that's relevant here:
some object in another array has a tab property whose value is the same as the first object's id property.
When working with JS arrays, for "some" we have the some function. So, following the same top-down approach, we are going to write (assuming we know what the ID is):
In the same way as above, we can write this as
function doesSomeElementInObj2Match(id) {
obj2.some(obj2Element => tabFieldMatches(obj2Element, id))
}
or
obj2.some(function(obj2Element) { return tabFieldMatches(obj2Element, id); })
Here, tabFieldMatches is nothing more than checking to make sure obj2Element.tab and id are identical.
We're almost done! but we still have to write hasMatchingTabField. That's quite easy, it turns out:
function hasMatchingTabField(e2, id) { return e2.tab === id; }
In the following, to save space, we will write e1 for obj1Element and e2 for obj2Element, and stick with the arrow functions. This completes our first solution. We have
const tabFieldMatches = (tab, id) { return tab === id; }
const hasMatchingTabField = (obj, id) => obj.some(e => tabFieldMatches(e.tab, id);
const findMatches = obj => obj.some(e => hasMatchingTabField(e1, obj.id));
And we call this using findMatches(obj1).
Old-fashioned array
But perhaps all these maps and somes are a little too much for you at this point. What ever happened to good old-fashioned for-loops? Yes, we can write things this way, and some people might prefer that alternative.
top: for (e1 of obj1) {
for (e2 of (obj2) {
if (e1.id === e2.tab) {
console.log("found match");
break top;
}
}
console.log("didn't find match);
}
But some people are sure to complain about the non-standard use of break here. Or, we might want to end up with an array of boolean parallel to the input array. In that case, we have to be careful about remembering what matched, at what level.
const matched = [];
for (e1 of obj1) {
let match = false;
for (e2 of obj2) {
if (e1.id === e2.tab) match = true;
}
matched.push(match);
}
We can clean this up and optimize it bit, but that's the basic idea. Notice that we have to reset match each time through the loop over the first object.

Steam API, different json format

Recently i integrated CSGO stats in my discord bot, but today i saw that for almost every player the API sends a different json data.
Here 2 examples:
https://jsonblob.com/58688d30-26d0-11e8-b426-7b3214778399
https://jsonblob.com/52ed0c3f-26d0-11e8-b426-43058df4a5a6
My question was how to request the data properly so a win is really a win and not a kill.
.addField('**Wins:**', `${object.playerstats.stats[5].value}`, true)
.addField('**Time played:**', `${object.playerstats.stats[2].value}` + ' minutes', true)
.addField('**Kills:**', `${object.playerstats.stats[0].value}`, true)
.addField('**Deaths:**', `${object.playerstats.stats[1].value}`, true)
.addField('**Bombs planted:**',`${object.playerstats.stats[3].value}`, true)
.addField('**Money earned:**',`${object.playerstats.stats[7].value}`, true)
.addField('**Knife kills:**',`${object.playerstats.stats[9].value}`, true)
.addField('**Headshot kills:**',`${object.playerstats.stats[24].value}`, true)
.addField('**Dominations:**',`${object.playerstats.stats[39].value}`, true)
.addField('**Rounds played:**',`${object.playerstats.stats[44].value}`, true)
The name property of stats items appear to be unique enough to find. You can use array.find to look for the correct stat by name.
const stats = object.playerstats.stats
const totalKills = stats.find(s => s.name === 'total_kills').value
const totalDeaths = stats.find(s => s.name === 'total_deaths').value
Taking it further, you can use array.reduce to generate an object whose key is name and value is value for each item in the array. This way, you access it like an object.
const stats = object.playerstats.stats
const statsObj = stats.reduce((c, e) => (c[e.name] = e.value, c), {})
const totalKills = statsObj.total_kills
const totalDeaths = statsObj.total_deaths
Rather than trying to reference the array indexes, why not convert the API response into an easier-to-parse format?
// do this once...
let playerStats = {};
object.playerstats.stats.forEach(s => playerStats[s.name] = s.value);
// ...then you can use the playerStats variable however you need:
.addField('**Kills:**', `${playerStats.total_kills}`, true)
.addField('**Wins:**', `${playerStats.total_wins}`, true)
The stats array is just not sorted. you can use .find() to get the correct entry from the stats.
for example
const totalWins = object.playerstats.stats.find(stat => {
return stat.name === 'total_wins';
});
.addField('**Wins:**', `${totalWins.value}`, true)
You are approching this problem the wrong way.
JSON is not a format that is ordered. What that means is that there is no guarantee that the JSON data will return in the same order everytime. It is not a default of the API.
There is one way you could still use your way: by sorting the 'stats' array by name. but it is a long operation and not a very good idea.
The way do to this is to do a lookup by name.
For example, if you want to find the wins, you do this :
object.playerstats.stats.find(elem => elem.name === 'total_wins').value;
The find function does a lookup and returns the first element matching the predicate (elem.name === 'total_wins'). It returns null if not element matched the predicate (so be careful here).
You could do a function that returns a value for you :
findValue(statsArray, name) {
const entry = statsArray.find(elem => elem.name === name);
return entry ? entry.value : '?';
}
And then your code would look like this :
...
.addField('**Wins:**', findValue(object.playerstats.stats, 'total_wins'), true)
...
The main thing here is : never assume fields in a JSON will return the same every time. Always use lookup, and not indexes (unless it is sorted).

CKEditor JSON Output [duplicate]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
I'm attempting map HTML into JSON with structure intact. Are there any libraries out there that do this or will I need to write my own? I suppose if there are no html2json libraries out there I could take an xml2json library as a start. After all, html is only a variant of xml anyway right?
UPDATE: Okay, I should probably give an example. What I'm trying to do is the following. Parse a string of html:
<div>
<span>text</span>Text2
</div>
into a json object like so:
{
"type" : "div",
"content" : [
{
"type" : "span",
"content" : [
"Text2"
]
},
"Text2"
]
}
NOTE: In case you didn't notice the tag, I'm looking for a solution in Javascript
I just wrote this function that does what you want; try it out let me know if it doesn't work correctly for you:
// Test with an element.
var initElement = document.getElementsByTagName("html")[0];
var json = mapDOM(initElement, true);
console.log(json);
// Test with a string.
initElement = "<div><span>text</span>Text2</div>";
json = mapDOM(initElement, true);
console.log(json);
function mapDOM(element, json) {
var treeObject = {};
// If string convert to document Node
if (typeof element === "string") {
if (window.DOMParser) {
parser = new DOMParser();
docNode = parser.parseFromString(element,"text/xml");
} else { // Microsoft strikes again
docNode = new ActiveXObject("Microsoft.XMLDOM");
docNode.async = false;
docNode.loadXML(element);
}
element = docNode.firstChild;
}
//Recursively loop through DOM elements and assign properties to object
function treeHTML(element, object) {
object["type"] = element.nodeName;
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object["content"] = [];
for (var i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType == 3) {
object["content"].push(nodeList[i].nodeValue);
} else {
object["content"].push({});
treeHTML(nodeList[i], object["content"][object["content"].length -1]);
}
}
}
}
if (element.attributes != null) {
if (element.attributes.length) {
object["attributes"] = {};
for (var i = 0; i < element.attributes.length; i++) {
object["attributes"][element.attributes[i].nodeName] = element.attributes[i].nodeValue;
}
}
}
}
treeHTML(element, treeObject);
return (json) ? JSON.stringify(treeObject) : treeObject;
}
Working example: http://jsfiddle.net/JUSsf/ (Tested in Chrome, I can't guarantee full browser support - you will have to test this).
​It creates an object that contains the tree structure of the HTML page in the format you requested and then uses JSON.stringify() which is included in most modern browsers (IE8+, Firefox 3+ .etc); If you need to support older browsers you can include json2.js.
It can take either a DOM element or a string containing valid XHTML as an argument (I believe, I'm not sure whether the DOMParser() will choke in certain situations as it is set to "text/xml" or whether it just doesn't provide error handling. Unfortunately "text/html" has poor browser support).
You can easily change the range of this function by passing a different value as element. Whatever value you pass will be the root of your JSON map.
htlm2json
Representing complex HTML documents will be difficult and full of corner cases, but I just wanted to share a couple techniques to show how to get this kind of program started. This answer differs in that it uses data abstraction and the toJSON method to recursively build the result
Below, html2json is a tiny function which takes an HTML node as input and it returns a JSON string as the result. Pay particular attention to how the code is quite flat but it's still plenty capable of building a deeply nested tree structure – all possible with virtually zero complexity
const Elem = e => ({
tagName:
e.tagName,
textContent:
e.textContent,
attributes:
Array.from(e.attributes, ({name, value}) => [name, value]),
children:
Array.from(e.children, Elem)
})
const html2json = e =>
JSON.stringify(Elem(e), null, ' ')
console.log(html2json(document.querySelector('main')))
<main>
<h1 class="mainHeading">Some heading</h1>
<ul id="menu">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
<p>some text</p>
</main>
In the previous example, the textContent gets a little butchered. To remedy this, we introduce another data constructor, TextElem. We'll have to map over the childNodes (instead of children) and choose to return the correct data type based on e.nodeType – this gets us a littler closer to what we might need
const TextElem = e => ({
type:
'TextElem',
textContent:
e.textContent
})
const Elem = e => ({
type:
'Elem',
tagName:
e.tagName,
attributes:
Array.from(e.attributes, ({name, value}) => [name, value]),
children:
Array.from(e.childNodes, fromNode)
})
const fromNode = e => {
switch (e?.nodeType) {
case 1: return Elem(e)
case 3: return TextElem(e)
default: throw Error(`unsupported nodeType: ${e.nodeType}`)
}
}
const html2json = e =>
JSON.stringify(Elem(e), null, ' ')
console.log(html2json(document.querySelector('main')))
<main>
<h1 class="mainHeading">Some heading</h1>
<ul id="menu">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
<p>some text</p>
</main>
Anyway, that's just two iterations on the problem. Of course you'll have to address corner cases where they come up, but what's nice about this approach is that it gives you a lot of flexibility to encode the HTML however you wish in JSON – and without introducing too much complexity
In my experience, you could keep iterating with this technique and achieve really good results. If this answer is interesting to anyone and would like me to expand upon anything, let me know ^_^
Related: Recursive methods using JavaScript: building your own version of JSON.stringify
json2html
Above we go from HTML to JSON and now we can go from JSON to HTML. When we can convert between two data types without losing data, this is called an isomorphism. All we are essentially doing here is writing the inverses of each function above -
const HtmlNode = (tagName, attributes = [], children = []) => {
const e = document.createElement(tagName)
for (const [k, v] of attributes) e.setAttribute(k, v)
for (const child of children) e.appendChild(toNode(child))
return e
}
const TextNode = (text) => {
return document.createTextNode(text)
}
const toNode = t => {
switch (t?.type) {
case "Elem": return HtmlNode(t.tagName, t.attributes, t.children)
case "TextElem": return TextNode(t.textContent)
default: throw Error("unsupported type: " + t.type)
}
}
const json2html = json =>
toNode(JSON.parse(json))
const parsedJson =
{"type":"Elem","tagName":"MAIN","attributes":[],"children":[{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"H1","attributes":[["class","mainHeading"]],"children":[{"type":"TextElem","textContent":"Some heading"}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"UL","attributes":[["id","menu"]],"children":[{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/a"]],"children":[{"type":"TextElem","textContent":"a"}]}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/b"]],"children":[{"type":"TextElem","textContent":"b"}]}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"LI","attributes":[],"children":[{"type":"Elem","tagName":"A","attributes":[["href","/c"]],"children":[{"type":"TextElem","textContent":"c"}]}]},{"type":"TextElem","textContent":"\n "}]},{"type":"TextElem","textContent":"\n "},{"type":"Elem","tagName":"P","attributes":[],"children":[{"type":"TextElem","textContent":"some text"}]},{"type":"TextElem","textContent":"\n"}]}
document.body.appendChild(toNode(parsedJson))
I got few links sometime back while reading on ExtJS full framework in itself is JSON.
http://www.thomasfrank.se/xml_to_json.html
http://camel.apache.org/xmljson.html
online XML to JSON converter : http://jsontoxml.utilities-online.info/
UPDATE
BTW, To get JSON as added in question, HTML need to have type & content tags in it too like this or you need to use some xslt transformation to add these elements while doing JSON conversion
<?xml version="1.0" encoding="UTF-8" ?>
<type>div</type>
<content>
<type>span</type>
<content>Text2</content>
</content>
<content>Text2</content>
Thank you #Gorge Reith. Working off the solution provided by #George Reith, here is a function that furthers (1) separates out the individual 'hrefs' links (because they might be useful), (2) uses attributes as keys (since attributes are more descriptive), and (3) it's usable within Node.js without needing Chrome by using the 'jsdom' package:
const jsdom = require('jsdom') // npm install jsdom provides in-built Window.js without needing Chrome
// Function to map HTML DOM attributes to inner text and hrefs
function mapDOM(html_string, json) {
treeObject = {}
// IMPT: use jsdom because of in-built Window.js
// DOMParser() does not provide client-side window for element access if coding in Nodejs
dom = new jsdom.JSDOM(html_string)
document = dom.window.document
element = document.firstChild
// Recursively loop through DOM elements and assign attributes to inner text object
// Why attributes instead of elements? 1. attributes more descriptive, 2. usually important and lesser
function treeHTML(element, object) {
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object[element.nodeName] = [] // IMPT: empty [] array for non-text recursivable elements (see below)
for (var i = 0; i < nodeList.length; i++) {
// if final text
if (nodeList[i].nodeType == 3) {
if (element.attributes != null) {
for (var j = 0; j < element.attributes.length; j++) {
if (element.attributes[j].nodeValue !== '' &&
nodeList[i].nodeValue !== '') {
if (element.attributes[j].name === 'href') { // separate href
object[element.attributes[j].name] = element.attributes[j].nodeValue;
} else {
object[element.attributes[j].nodeValue] = nodeList[i].nodeValue;
}
}
}
}
// else if non-text then recurse on recursivable elements
} else {
object[element.nodeName].push({}); // if non-text push {} into empty [] array
treeHTML(nodeList[i], object[element.nodeName][object[element.nodeName].length -1]);
}
}
}
}
}
treeHTML(element, treeObject);
return (json) ? JSON.stringify(treeObject) : treeObject;
}
I had a similar issue where I wanted to represent HTML as JSON in the following way:
For HTML text nodes, use a string
For HTML elements, use an array with:
The (tag) name of the element
An object, mapping attribute keys to attribute values
The (inlined) list of children nodes
Example:
<div>
<span>text</span>Text2
</div>
becomes
[
'div',
{},
['span', {}, 'text'],
'Text2'
]
I wrote a function which handles transforming a DOM Element into this kind of JS structure. You can find this function at the end of this answer. The function is written in Typescript. You can use the Typescript playground to convert it to clean JavaScript.
Furthermore, if you need to parse an html string into DOM, assign to .innerHtml:
let element = document.createElement('div')
element.innerHtml = htmlString
Also, this one is common knowledge but if you need a JSON string output, use JSON.stringify.
/**
* A NodeDescriptor stands for either an (HTML) Element, or for a text node
*/
export type NodeDescriptor = ElementDescriptor | string
/**
* Array representing an HTML Element. It consists of:
*
* - The (tag) name of the element
* - An object, mapping attribute keys to attribute values
* - The (inlined) list of children nodes
*/
export type ElementDescriptor = [
string,
Record<string, string>,
...NodeDescriptor[]
]
export let htmlToJs = (element: Element, trim = true): ElementDescriptor => {
let convertElement = (element: Element): ElementDescriptor => {
let attributeObject: Record<string, string> = {}
for (let { name, value } of element.attributes) {
attributeObject[name] = value
}
let childArray: NodeDescriptor[] = []
for (let node of element.childNodes) {
let converter = htmlToJsDispatch[node.nodeType]
if (converter) {
let descriptor = converter(node as any)
let skip = false
if (trim && typeof descriptor === 'string') {
descriptor = descriptor.trim()
if (descriptor === '') skip = true
}
if (!skip) childArray.push(descriptor)
}
}
return [element.tagName.toLowerCase(), attributeObject, ...childArray]
}
let htmlToJsDispatch = {
[element.ELEMENT_NODE]: convertElement,
[element.TEXT_NODE]: (node: Text): string => node.data,
}
return convertElement(element)
}

Categories

Resources