I'm trying to generate a tree structure in JavaScript from a flat array. This would usually be a fairly straightforward proposition - simply retain a 'stack' array with references to ancestor objects of the current working scope ordered by nesting depth - push a new element onto the stack when entering another nested level, and pop it off when leaving one, replacing the current working element with the object referenced by the (new) last array item.
Unfortunately this requires the capability to pass-by-reference, which JavaScript doesn't have (well, doesn't have in any meaningful way that I know how I could use for this problem.)
To give a bit of background, I'm trying to turn an arbitrarily long/complicated string containing nested XML-style (but not XML, so an XML parser can't be used instead) tokens into a structure similar to the one below:
Expected Input:
[
"<token>",
"<my non compliant token>",
"some text at this level",
"<some other token>",
"some more text",
"<yet another token>",
"more text",
"</yet another token>",
"blah!",
"</some other token>",
"</token>",
"more text"
]
Expected Output
[
{
"token": "<token>",
"children": [
{
"token": "<my non compliant token>",
"children": [
"some text at this level",
{
"token": "<some other token>",
"children": [
"some more text",
{
"token": "<yet another token>",
"children": [ "more text" ]
},
"blah!"
]
}
]
}
]
},
"more text"
]
To clarify - I'm not after an entire algorithm (but I'd be interested if you want to provide your implementation) - just a good method for maintaining current position in the outputted tree (or an entirely different/better way of generating tree objects!) Don't get too caught up on how the tokens work - they're not XML and for the purpose of the exercise could be formatted entirely differently.
Any input would be greatly appreciated!
Your strings look easy to parse. I think I would do something like this:
var stack = [];
var array = [];
for (var i in strings) {
var s = strings[i];
if (s.indexOf("</") == 0) {
array = stack.pop();
} else if (s.indexOf("<") == 0) {
var obj = {token: s, children: []};
array.push(obj);
stack.push(array);
array = obj.children;
} else {
array.push(s);
}
}
Idea #1
Here's an answer you probably weren't anticipating.
Looking at your expect output, I was wondering if it's easiest to just generate JSON and then eval it when you're done. No references at all.
When going through your flat array, you basically have three operations:
You add more data to the current object
You close off the current object
You create a new child object
You can do all three of those fairly easily by just appending the appropriate text onto a JSON string you're building as you iterate through your source array to literally just generate the text you show in your expected output. When done, run that JSON string through eval. You may need a few safety checks to verify that each array and object is closed properly if there are errors in the input, but it should work fine.
Idea #2
You can still use your stack array. I'm not sure exactly why you need to pass by reference, but you can just pass an index into the array around and have everyone modify the master copy of the array that way via index. Using local functions, the master array can be a common data value that is local to your main function, but essentially global to all your sub-functions so they can all shared access to it.
This would look something like this:
function ParseRawData(rawData)
{
var parentScopeArray = []; // main parent scope of objects
function processTag(x)
{
// you can access parentScopeArray directly here and
// and be accessing it by reference
}
// other code or local functions here
}
Idea #3
If you want to pass the array into a function and have the master copy modified (perhaps the reason you're thinking of pass by reference), the javascript design pattern is to pass the array in and return a modified array, replacing the entire original array with the modified one that is returned.
Related
I'm not looking for the keys that this object contains but the key of the object itself (the key in the array containing the object).
I have this JSON:
{
"Object name (text)": {
"raw": "Some more text.",
},
"Another name": {
"raw": "Some other text.",
}
}
and would like to get "Object name (text)" for the first item.
My Vue code is:
<CustomComponent
v-for="object in objects"
:key="getKey(object)"
:object="object"
/>
I'm not sure if the getKey-method approach is how one is intended to get unique identifiers for iterating through the JSON array. Its code currently is:
getKey(object) {
return Object.keys(object)[0];
}
Now I'd like to somehow pass the name of the object to the CustomComponent ("Object name (text)" in the first case).
One temporary workaround that I intended to use until I find something more appropriate was getting the keys from the objects array like so:
:objectName="getObjectName(object)" and itemNumber: -1 in data and this method:
getObjectName(object) {
this.itemNumber = this.itemNumber + 1;
var objectName = Object.keys(this.objects)[this.itemNumber];
console.log("Object name: ", objectName);
}
However, the first line of this method causes it to run hundreds of times instead of only two times (why is that?; it works in the first 2 executions of the method and when commenting out that line) and I think this is unlikely the proper method to simply retrieve the object's name/key.
It also didn't work when putting the above code into the getKey method which would make more sense (and I had the code in that method before creating a separate method to debug). Then the key could be accessed in the component with this.$vnode.key However, it keeps being undefined. This might be a separate problem even though it could resolve this problem here as well - I might create a new question for it. It enters the methods "getKey" and "getObjectName" 6 times each even though it only renders two items on the page, like it should.
-> How to get the JSON object's key in JavaScript?
(Preferably from the object itself after iterating through a JSON array with a loop with Vue instead of only indirectly by checking the objects array.)
Edit: as a workaround I have now done this:
var keys = Object.keys(this.objects);
keys.forEach(element => {
this.objectsWithKeys.push({
object: this.objects[element],
key: element
});
});
<CustomComponent
v-for="objectWithKeys in objectsWithKeys"
:key="objectWithKeys.key"
:object="objectWithKeys.object"
>
</CustomComponent>
this.$vnode.key
This is solved, I used var objectsWithKeys = data[Object.keys(data)]; and {{ $vnode.key }}.
I am using JSON to carry information from NodeJS (server side) to my client side. When I try to parse the JSON in the client side, it outputs only the last element, in this case, 'name: "Sam"'. I want all the elements to be outputted.
I have tried using an array and a variable to be assigned the parsed data. I have also directly tried logging it to the console with: [console.log(JSON.parse(this.response));]. All three gave the same result.
The first console.log returns all the elements in JSON form. The second one returns only the last one. There are 3 elements in total.
I expect all the elements to be assigned to the variable.
request.open('GET', 'http://localhost:3000/listofvoted', true);
request.onload = function () {
console.log(this.response)
console.log(JSON.parse(this.response));
}
request.send();
The JSON I receive:
{
"name": "Bran",
"name": "Ram",
"name": "Sam"
}
Although JSON (which is just a notation) allows for duplicate key names, the guidance is that that they SHOULD be unique. If you want to use JSON to create a JavaScript Object, then you are constrained by the fact that a JavaScript Object cannot have duplicate keys. So although you have valid JSON, it cannot be represented by a JavaScript Object and therefore it will not survive a round trip of being parsed (JSON converted to a JavaScript Object) by JSON.parse and then converted back to JSON.
For your own convenience of working in JavaScript, you could consider changing the JSON representation of your information so that it can be represented as a JavaScript Object.
Here are some alternative ways of representing what you have that may work:
Use an array of discrete objects:
[
{ "name": "Bran" },
{ "name": "Ram" },
{ "name": "Sam }"
]
Use an array of names:
{
"names": [ "Bran", "Ram", "Sam" ]
}
As a final, heavy-handed approach, you don't have to convert your JSON to a JavaScript object. You can parse it using a parser that allows you to provide your own handlers for the syntactic elements that occur in the JSON string and you can handle the duplicate keys in whatever way you wish. May I suggest the clarinet library for doing so.
See also, How to get the JSON with duplicate keys completely in javascript
Using const token = response.json().token; I am able to get the following JSON:
{
"token": "*********",
"roles": [
{
"id": 4,
"name": "User",
"pivot": {
"user_id": 1,
"role_id": 4
}
}
]
}
I want to be able to access the names within the roles as an array.
If you just want to access the array of roles, assuming you're using a traditional response object, you can just access it the way other users have stated:
var roles = response.json().roles
As I reread the question and comments, I get the idea that the user wants to access the names within the roles as as list. Assuming so, the map function will do this nicely:
// Assuming we store response in "data"
var data = response.json();
var names = data.roles.map(function(role){return role.name});
console.log(names);
// Then "names" will look like ["User",...]
In a nutshell, map will walk the array it's called against and run the provided function against it, with the first argument being the current value it sees in the array. This function can be defined on the fly as above, or predefined and passed if the logic is complex or reused.
This is a very common use for Map, and its sibling, Reduce. Both are often used for distilling complex, variable-length data down to a simpler form.
Map documentiation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Update: It would appear the original question was asked regarding ES6, so here's the "proper" ES6 version with proper declarations and arrow funcs:
// Assuming we store response in "data"
const data = response.json();
const names = data.roles.map(role => role.name);
console.log(names);
// Then "names" will look like ["User",...]
If I have a Javascript object, parsed from JSON, that’s nested three deep, and I don’t know the key for the middle one, how to I access it and its contents?
The actual data I’m working with is from the Github API. For this example, I want the file names for all my gists.
[
{
"url": "https://api.github.com/gists/11164200",
"forks_url": "https://api.github.com/gists/11164200/forks",
"commits_url": "https://api.github.com/gists/11164200/commits",
"id": "11164200",
"git_pull_url": "https://gist.github.com/11164200.git",
"git_push_url": "https://gist.github.com/11164200.git",
"html_url": "https://gist.github.com/11164200",
"files": {
"testing.md": {
"filename": "testing.md",
"type": "text/plain",
"language": "Markdown",
"raw_url": "https://gist.githubusercontent.com/omphalosskeptic/11164200/raw/3582779a4925ea514382cedb7d077d00c231f3eb/testing.md",
"size": 4254
}
}, // [ ... continues]
My Javascript skills are rudimentary. Normally I can find what I’m looking for with enough research but not this time. Originally I expected it would be something like: responseObj[0].files[0].filename.
If possible I’d like to keep this plain Javascript.
Thanks!
Based on the sample you posted, the files property is not an array, so can't be accessed by an indexer. This is a case where you would use a for-in loop rather than a regular for loop.
for(var p in responseObj[0].files) {
if ( responseObj[0].files.hasOwnProperty (p) ) {
p; // p is your unknown property name
responseObj[0].files[p]; // is the object which you can use to access
// its own properties (filename, type, etc)
}
}
The hasOwnProperty check will skip the automatic members like toString and only return those manually defined on the object.
If you want to iterate over them, do something like
for (var fileID in responseObj[0].files) {
var file = responseObj[0].files[fileID];
var filename = file.filename;
}
I have a json output that looks like this.
{
"38467": {
"name": "Tony Parker",
"book": [
{
"title": {
"name": "MediaWorks"
},
},
],
}
"59678": {
"name": "Ray Thomas",
}
}
Actually json output is a lot bigger list. But I want to only save author's name and their publisher. I want save multiple individual records for a single model.
I am using jquery to assign input elements their values.
$('#Author' + i + 'Title').val(response...);
But it is not working.
I appreciate any help.
Thanks.
JSON is just javascript data - a javascript object. Assign the "decoded" JSON data to a variable (say var dat), then you can access members the normal object/array away: dat[38467]['name'] is Tony Parker and so on.
comment update:
once you've decoded/stored the data, you can use a regular javascript foreach loop to go over it:
for (var bookID in booklist) {
var author = booklist[bookID]['name'];
var title = booklist[bookID]['book'][0]['title']['name'];
// ...do stuff here...
}
There's nothing magical about JSON, it's just javascript data packed up for easy/clean transmission. Of course, if you're using a framework such as jQuery or MooTools, you'd be better off using their own .each() operators, otherwise you'll get various bits of useless fluff from the for() loop.
edit: fixed code sample as per marimuthu's comment (good catch, thanks).