How to rearrange the JSON object that Artoo.js generates? - javascript

I have a very simple file that should scrape a single webpage for some data. After reading around I set myself to artoo, request and cheerio but now I'm stuck. This is the code I have so far.
request('http://www.ciclopi.eu/frmLeStazioni.aspx?ID=144', function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html);
artoo.bootstrap(cheerio);
var scraper = $('span.Stazione, span.TableComune, span.Red').scrape({
class: 'class',
content: 'text'
});
console.log(scraper);
}
});
This code resolves into the scraper variable as a json object structured like this:
[ { class: 'Stazione', content: 'Galleria Gerace' },
{ class: 'TableComune', content: 'Via Carlo Matteucci' },
{ class: 'Red', content: '7 bici libere5 posti disponibili' },
{ class: 'Stazione', content: 'C. Marchesi' },
{ class: 'TableComune', content: 'Via M. Valgimigli' },
{ class: 'Red', content: '2 bici libere10 posti disponibili' },
{ class: 'Stazione', content: 'CNR-Praticelli' },
{ class: 'TableComune', content: 'Via G. Moruzzi' },
{ class: 'Red', content: '7 bici libere7 posti disponibili' } ]
What I need is to rearrange the scraper object as something like this
scraper = [
{
"Name": the content for class Stazione,
"Address": the content for class TableComune,
"Bikes": the content for class Red
},
{
"Name": the content for class Stazione,
"Address": the content for class TableComune,
"Bikes": the content for class Red
}
...
]
I'm really in the dark here, hopefully I explained myself..

For this kind of mutations I would use Array.prototype.reduce method.
const data = [
{ class: 'Stazione', content: 'Galleria Gerace' },
{ class: 'TableComune', content: 'Via Carlo Matteucci' },
{ class: 'Red', content: '7 bici libere5 posti disponibili' },
{ class: 'Stazione', content: 'C. Marchesi' },
{ class: 'TableComune', content: 'Via M. Valgimigli' },
{ class: 'Red', content: '2 bici libere10 posti disponibili' },
{ class: 'Stazione', content: 'CNR-Praticelli' },
{ class: 'TableComune', content: 'Via G. Moruzzi' },
{ class: 'Red', content: '7 bici libere7 posti disponibili' }
];
const result = data.reduce((acc, item) => {
if (item.class === 'Stazione') {
acc.push({ 'Name': item.content });
} else {
const last = acc[acc.length - 1];
if (item.class === 'TableComune') {
last['Address'] = item.content;
} else { // if (item.class === 'Red')
last['Bikes'] = item.content;
}
}
return acc;
}, []);
Here we are iterating through the initial data array and generating the result array by 2 conditional points:
if current item is Stazione then we are pushing a new object to the result array; this object will have Name property corresponded to current Stazione content
otherwise we are updating the last element of the result array: TableComune content will move to Address property, Red content will move to Bikes property.
The procedure will work only in case of strict order of Stazione-TableComune-Red objects in the initial data array.

Related

JSON: how do you add to a field that doesn't have a key?

I have a simple JSON object
simple_chart_config = {
chart: {
container: "#tree-simple"
},
nodeStructure: {
text: { name: "Question" },
children: [
{
text: { name: "Answer 1" }
}
]
}
};
And I'd like to add a new subfield within the first entry on the children array so that the final output is
simple_chart_config = {
chart: {
container: "#tree-simple"
},
nodeStructure: {
text: { name: "Question" },
children: [
{
text: { name: "Answer 1" },
children: [
{
text: { name: "Question 2" }
}
]
}
]
}
};
I've tried several methods, such as
var questionTwoStr = '{"children": [{"text": { "name": "Question 2" }}]}'
var questionTwo = JSON.parse(questionTwoStr);
simple_chart_config.nodeStructure.children[0] = questionTwo;
but I'm having issues working out all of the nested indexes in my head. This is for a tree in treant.js if that context is helpful at all.
I think I'm mostly confused because the place I'm trying to add the new subfield doesn't have a key, which I thought was required for JSON.
There's no need to use JSON here; you can add the object itself:
simple_chart_config.nodeStructure.children[0].children = [{text: { name:"Question 2" }}];

How to access an object from an object nested function with this keyword?

I have this object:
popup_data = {
club: {
type: 'club',
type_img: {
header: 'CLUB HEADER',
img: 'https://source.unsplash.com/random/800x600',
sub_header: 'CLUB SUB HEADER',
content: 'TEXT',
hotspot_position: [5, -1.5, 2.5]
},
hotspots_array: [
{ id: () => this.club.type + '-' + 'type_img',
position: () => this.club.type_img.hotspot_position,
},
]
},
How to get type and type_img.hotspot_position from these nested functions
just use the var name, popup_data
popup_data = {
club: {
type: 'club',
type_img: {
header: 'CLUB HEADER',
img: 'https://source.unsplash.com/random/800x600',
sub_header: 'CLUB SUB HEADER',
content: 'TEXT',
hotspot_position: [5, -1.5, 2.5]
},
hotspots_array: [
{ id: () => popup_data.club.type + '-' + 'type_img',
position: () => popup_data.club.type_img.hotspot_position,
},
]
},
Read This and This
What is this?
In JavaScript, the this keyword refers to an object.
Which object depends on how this is being invoked (used or called).
The this keyword refers to different objects depending on how it is used:
In an object method, this refers to the object.
Alone, this refers to the global object.
In a function, this refers to the global object.
In a function, in strict mode, this is undefined.
In an event, this refers to the element that received the event.
Methods like call(), apply(), and bind() can refer this to any object.
You can access it by the variable name that you assign the entire object into.
popup_data = {
club: {
type: 'club',
type_img: {
header: 'CLUB HEADER',
img: 'https://source.unsplash.com/random/800x600',
sub_header: 'CLUB SUB HEADER',
content: 'TEXT',
hotspot_position: [5, -1.5, 2.5]
},
hotspots_array: [
{ id: () => popup_data.club.type + '-' + 'type_img',
position: () => popup_data.club.type_img.hotspot_position,
},
]
},
Since you are using ES6 arrow function, this will not work at all. And if you used normal function, this would be the object inside hotspots_array, not the entire object.
If you have more than one item, and want all those items to have access to the root, you can bind the function. You will need to use none arrow functions here, as they don't have a this..
You can even extract your id and position function out of the object too, so that your not creating multiple instances of the exact same function.
eg..
function id() {
return this.club.type + '-' + 'type_img';
}
function position() {
return this.club.type_img.hotspot_position;
}
const popup_data = {
club: {
type: 'club',
type_img: {
header: 'CLUB HEADER',
img: 'https://source.unsplash.com/random/800x600',
sub_header: 'CLUB SUB HEADER',
content: 'TEXT',
hotspot_position: [5, -1.5, 2.5]
},
hotspots_array: [
{
name: 'test',
id, position
}, {
name: 'test2',
id, position
}
]
}
};
const popup_data_2 = {
club: {
type: 'this is club 2',
type_img: {
hotspot_position: [5, -1.5, 2.5]
},
hotspots_array: [
{
id, position
}
]
}
};
function bindFunctions(r, a) {
Object.entries(a).map(([k, v]) => {
const tp = typeof v;
if (tp === 'function') {
a[k] = v.bind(r);
} else if (tp === 'object') {
bindFunctions(r, v);
}
});
}
bindFunctions(popup_data, popup_data);
bindFunctions(popup_data_2, popup_data_2);
console.log(popup_data.club.hotspots_array[0].id());
console.log(popup_data.club.hotspots_array[0].position());
console.log(popup_data_2.club.hotspots_array[0].id());
Another option, having a single root item can be restrictive. If you wanted something more fancy I would create your Object using a function and use the power of closures. The example below I've added another prop that then has access to it's array.
function makeObject(data) {
const root = data;
for (const h of data.club.hotspots_array) {
//now lets add our functions
h.id = () => root.club.type + '-' + 'type_img';
h.position = () => root.club.type_img.hotspot_position;
h.array = data.club.hotspots_array;
}
return root;
}
const o1 = makeObject({
club: {
type: 'club',
type_img: {
header: 'CLUB HEADER',
img: 'https://source.unsplash.com/random/800x600',
sub_header: 'CLUB SUB HEADER',
content: 'TEXT',
hotspot_position: [5, -1.5, 2.5]
},
hotspots_array: [
{
name: 'test',
}, {
name: 'test2',
}
]
}
});
console.log(o1.club.hotspots_array[0].id());
console.log(o1.club.hotspots_array[0].position());
console.log('array:' + o1.club.hotspots_array[0].array.length);

How to add a unique ID to each entry in my JSON object?

I have this array of JSON objects:
and I want to add a unique ID (string) to each entry, like this:
let myTree = [
{
text: 'Batteries',
id: '0',
children: [
{
text: 'BatteryCharge',
id: '0-0'
},
{
text: 'LiIonBattery',
id: '0-1'
}
]
},
{
text: 'Supplemental',
id: '1',
children: [
{
text: 'LidarSensor',
id: '1-0',
children: [
{
text: 'Side',
id: '1-0-0'
},
{
text: 'Tower',
id: '1-0-1'
}
]
}
]
}
]
I just can't think of the right logic to achieve this. I have written this recursive function, which obviously does not achieve what I want:
function addUniqueID(tree, id=0) {
if(typeof(tree) == "object"){
// if the object is not an array
if(tree.length == undefined){
tree['id'] = String(id);
}
for(let key in tree) {
addUniqueID(tree[key], id++);
}
}
}
addUniqueID(myTree);
How can I solve this problem?
Instead of using a number/id in the recursive function I build a string.
let myTree = [{
text: 'Batteries',
children: [{
text: 'BatteryCharge'
},
{
text: 'LiIonBattery'
}
]
},
{
text: 'Supplemental',
children: [{
text: 'LidarSensor',
children: [{
text: 'Side'
},
{
text: 'Tower'
}
]
}]
}
];
function addUniqueID(arr, idstr = '') {
arr.forEach((obj, i) => {
obj.id = `${idstr}${i}`;
if (obj.children) {
addUniqueID(obj.children, `${obj.id}-`);
}
});
}
addUniqueID(myTree);
console.log(myTree);
I hope you are well.
why don't you consider using uuid?
In node there is the uuid module which you can use to generate unique identifiers, I share a base example:
install:
npm install uuid
npm i --save-dev #types/uuid
code:
import {v4 as uuid} from 'uuid';
let _id = uuid();

How to add objects to an array during creation using a loop in Javascript?

I am trying to write some meta information for a website (using vue-meta) and I need to add some tags as objects within an array named meta.
The code is like this:
metaInfo() {
return {
htmlAttrs: { lang: "en"
},
title: this.Post.Title,
meta: [
{
name: "description", content: this.Post.Title
},
{
name: "date", content: this.Post.DateCreated
},
{
name: "author", content: this.Post.Author
},
// Now I need multiple objects of: {name: "tag", content: "Tags.TagName"} like this but doesn't work:
function() {
this.Tags.forEach(function (TagName, index) {
{ property: "tag", content: "TagName" }
})
}
],
}
}
How can I create my array so that I end up with this for example:
meta: [
{
name: "description", content: "Javascript question"
},
{
name: "date", content: "20200421"
},
{
name: "author", content: "volumeone"
},
{ property: "tag", content: "Javascript" }
,
{ property: "tag", content: "Programming" }
,
{ property: "tag", content: "Newbie" }
]
you can do such sort of thing.
var meta = [{
name: "description", content: this.Post.Title
},
{
name: "date", content: this.Post.DateCreated
},
{
name: "author", content: this.Post.Author
}]
this.Tags.forEach(function (TagName, index) {
meta.push({ property: "tag", content: "TagName" })
})
metaInfo() {
return {
htmlAttrs: { lang: "en"
},
title: this.Post.Title,
// or you can just write "meta" instead of "meta: meta" its an shorthand // code
meta: meta
}
}
Unless I'm missing something, you can just use push and pass the object.
var meta = [];
meta.push({"property" : "tag","content" : "test"});
console.log(meta);

Add tasks for todo list

I am trying to add tasks for each todo list that has a specific title.
Can I get a specific todo list by its id and add some tasks to it?
I am new to javascript, so I searched google about adding lists for a specific list with no results :(
class Model {
constructor() {}
this.todos = [
{
id: 1,
title: 'Outside',
text: 'Running',
complete: false,
tasks: [
{ id: 1, text: 'Run a marathon', complete: false},
{ id: 2, text: 'Run with freinds', complete: false}
]
},
{
id: 2,
title: 'Garden',
text: 'Plant',
complete: false,
tasks: [
{ id: 1, text: 'Plant a garden', complete: false},
{ id: 2, text: 'Water the garden', complete: false}
]
}];
addTodo(todoText) {
const todo = {
id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
text: todoText,
complete: false,
tasks: []
}
this.todos.push(todo)
}
}
Is it true to do like addTodo function for adding a tasks for a specific todo list like this?
addTodoTask(todoTaskText) {
const todoTask = {
id: this.todos.tasks.length > 0 ? this.todos[this.todos.tasks.length - 1].id + 1 : 1,
text: todoText,
complete: false,
}
this.todos.tasks.push(todoTask)
}
and how to add a list of a list in javascript like:
<ul>
<li>Running
<ul>
<li>Run a marathon</li>
<li>Run with freind</li>
</ul>
</li>
</ul>
You could make each class handle rendering its own content and just map the list items consecutively while rendering from the top-down.
Edit: The render() methods make use of ES6 template literals. These are special strings that allow you embed variabes and expressions without the use of string concatenation.
const main = () => {
let todoList = new TodoList({ todos : getData() })
document.body.innerHTML = todoList.render()
}
class TodoTask {
constructor(options) {
this.id = options.id
this.text = options.text
this.complete = options.complete
}
render() {
return `<li>[${this.id}] ${this.text} (${this.complete})</li>`
}
}
class TodoEntry {
constructor(options) {
this.id = options.id
this.title = options.title
this.text = options.text
this.complete = options.complete
this.tasks = []
if (options.tasks) {
options.tasks.forEach(task => this.addTask(task))
}
}
addTask(task) {
this.tasks.push(new TodoTask(Object.assign({
id : (this.tasks.length || 0) + 1
}, task)))
}
render() {
return `<li>
[${this.id}] ${this.title} (${this.complete})
<ul>${this.tasks.map(task => task.render()).join('')}</ul>
</li>`
}
}
class TodoList {
constructor(options) {
this.todos = []
if (options.todos) {
options.todos.forEach(todo => this.addTodo(todo))
}
}
addTodo(todo) {
this.todos.push(new TodoEntry(Object.assign({
id : (this.todos.length || 0) + 1
}, todo)))
}
render() {
return `<ul>${this.todos.map(todo => todo.render()).join('')}</ul>`
}
}
function getData() {
return [{
id: 1,
title: 'Outside',
text: 'Running',
complete: false,
tasks: [{
id: 1,
text: 'Run a marathon',
complete: false
}, {
id: 2,
text: 'Run with freinds',
complete: false
}]
}, {
id: 2,
title: 'Garden',
text: 'Plant',
complete: false,
tasks: [{
id: 1,
text: 'Plant a garden',
complete: false
}, {
id: 2,
text: 'Water the garden',
complete: false
}]
}]
}
main() // entry
To add a task your todo, you should have a way of knowing which todo list you're updating. Like using the todo's id.
For example your addTaskToTodo will looks like so.
addTask(todoId, taskObject) {
// find that todos index
const todoIndex = this.todos.findIndex(todo => todo.id ===todoId);
// using that index update the tasks
this.todos[todoIndex].tasks.push(taskObject)
}
This assumes your taskObject already has all the properties. If you need to manually update its id, you can also do that before pushing by checking the length of the tasks and incrementing by 1.
I made an example of how to use dictionaries instead of arrays, and also a random ID. I think you will find it much cleaner and simpler:
class Model {
constructor() { }
todos = {
1: {
id: 1,
title: 'Outside',
text: 'Running',
complete: false,
tasks: {
1: { id: 1, text: 'Run a marathon', complete: false },
2: { id: 2, text: 'Run with freinds', complete: false }
}
},
2: {
id: 2,
title: 'Garden',
text: 'Plant',
complete: false,
tasks: {
1: { id: 1, text: 'Plant a garden', complete: false },
2: { id: 2, text: 'Water the garden', complete: false }
}
}
}
getRandomId = () => {
return '_' + Math.random().toString(36).substr(2, 9);
}
addTodo(todoText) {
const id = this.getRandomId();
const todo = {
id,
text: todoText,
complete: false,
tasks:{}
}
this.todos[id] = todo;
}
addTodoTask(todoTaskText,todoId) {//Pass also the id of the todo, to know where this task belongs to.
const id = this.getRandomId();
const todoTask = {
id,
text: todoTaskText,
complete: false,
}
this.todos[todoId].tasks[id] = todoTask
}
}
This way you could easily edit/remove both todos and tasks, just by their id, without using any messy Array.filter and such

Categories