Accessing JSON by using a constant - javascript

My JSON looks as following:
{
flight_number: 1,
mission_name: "FalconSat",
rocket: {
rocket_id: "falcon1",
rocket_name: "Falcon 1",
rocket_type: "Merlin A",
first_stage: {
cores: [
{
core_serial: "Merlin1A",
flight: 1
}
]
},
fairings: {
reused: false,
recovery_attempt: false,
recovered: false,
ship: null
}
},
links: {
video_link: "https://www.youtube.com/watch?v=0a_00nJ_Y88",
flickr_images: [ ]
},
details: "Engine failure at 33 seconds and loss of vehicle"
}
To access the video link, I want to use a constant and I've been trying to use:
const links = "links";
const video1 = links + ".video_link"; // this doesn't work
const video2 = "links.video_link"; // this doesn't work
const det = "details";
getVideoURL(something) {
console.log(something.video1); // undefined
console.log(something.links.video_link); // this works
console.log(something.det); // this works
return something.video2;
}
getDetails(something) {
return something.det; // this works
}
// the jsonObject is retrieved by an async function, i just simplified it. I hope this makes sense.
const jsonObject = await axios("https://api.spacexdata.com/v3/launches");
let url = getVideoURL(jsonObject);
let det = getDetails(something);
console.log(url) // undefined
console.log(det) // prints out details.
Is there a way I can get the video URL like how I've done it with getDetails by using return something.video1 or return something.video2?

Use an array instead of a string, and then you can use reduce to iterate over each key in the array, accessing the appropriate nested property value, passing in the entire object as the initial value of the accumulator a:
const obj = {flight_number:1,mission_name:"FalconSat",rocket:{rocket_id:"falcon1",rocket_name:"Falcon 1",rocket_type:"Merlin A",first_stage:{cores:[{core_serial:"Merlin1A",flight:1}]},fairings:{reused:!1,recovery_attempt:!1,recovered:!1,ship:null}},links:{video_link:"https://www.youtube.com/watch?v=0a_00nJ_Y88",flickr_images:[]},details:"Engine failure at 33 seconds and loss of vehicle"};
const getVideoURL = obj => path.reduce((a, prop) => a[prop], obj);
const path = ['links', 'video_link'];
const url = getVideoURL(obj);
console.log(url);
Alternatively, if you wanted to use a string, you would have to split on .s first:
const pathString = "links.video_link";
const getVideoURL = obj => pathString
.split('.')
.reduce((a, prop) => a[prop], obj);

Related

Javascript Function: Use Variable instead of single Values

I found here a script. It works fine. But now, I want to use a Variable instead of single values.
Here the original script:
const customData = {
"func":"bri",
"oid":"ID",
"onVal":1,
"offVal":0,
"...":"..."
}
const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});
const Light.bri = getSubset(customData, "oid", "onVal", "offVal");
Result (OK):
bri: {
offVal: 0,
oid: "objekt-ID",
onVal: 1
},
Now I want to do define the keys in a variable, ideally as a object. But this do not work.
const params = {bri: "oid, onVal, offVal"};
const Light.bri = getSubset(customData, params.bri);
Result (NOK):
bri: {
oid, onVal, offVal: undefined
},
description: "Deckenspots"
}
what changes do I have to make?
Define the bri property as an array of strings. That way you can use the spread syntax (...) to pass the strings as individual arguments.
const params = {bri: ["oid", "onVal", "offVal"]}; // bri is now an array.
const Light.bri = getSubset(customData, ...params.bri); // Notice the ... before params.bri

How to check if an object has other than specific properties

I have an object obj which has n number of possible properties
lets say some of them are known,
const someKnownProps = ["props.abc", "xyz"]; // or more
I want to know if obj has other than known properties in it.
To clarify:
obj can look like this:
obj = {
props: {
abc: {
def: 1
},
ghi: {
jkl: 2
}
},
xyz: 3
}
Doing Object.keys only return first level children,
in this case it will return props not props.abc
You can use Object.keys to get all keys and filter the keys which aren't included in the someKnownProps array.
const obj = {
"props.abc": 1,
"xyz": 2,
"three": 3,
"four": 4,
}
const someKnownProps = ["props.abc", "xyz"]; // or more
const unknownKeys = Object.keys(obj).filter(key => !someKnownProps.includes(key))
console.log(unknownKeys)
There are two (unrelated) tasks involved in this question:
Traversal of an object's properties
Comparison of a set of traversed object properties to a list of strings representing dot-notation-formatted object property accessors
While I'm sure the former has been previously discussed on SO, I'll provide an implementation of such an algorithm below in order to address the details of this question.
This is essentially a specific case of recursion where each cycle starts with these inputs:
an object
a dot-notation-formatted path
a Set of existing such paths
The code below includes inline comments explaining what's happening, and there are some console.log statements at the end to help you visualize some example results based on the data in your question. If something is unclear after reviewing the code, feel free to leave a comment.
'use strict';
/** #returns whether value is a non-null, non-array object */
function isObject (value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/** #returns the enumerable (optionally including inherited) keys of an object */
function getKeys (obj, includeInherited = false) {
if (!includeInherited) return Object.keys(obj);
const keys = new Set();
let o = obj;
while (o !== null) {
for (const key of Object.keys(o)) keys.add(key);
o = Object.getPrototypeOf(o);
}
return [...keys];
}
/**
* #returns an array of strings representing all traversible branches
* of child objects, each formatted as a combined path of dot-notation
* property accessors
*/
function findObjectPaths (
obj,
{
includeInherited = false,
currentPath = '',
paths = new Set(),
skipReturn = false,
} = {},
) {
for (const key of getKeys(obj, includeInherited)) {
// Append the current dot-notation property accessor
// to the existing path of this object:
const path = `${currentPath}.${key}`;
// Add it to the set:
paths.add(path);
const o = obj[key];
// Recurse if the child value is an object:
if (isObject(o)) {
findObjectPaths(o, {
includeInherited,
currentPath: path,
paths,
skipReturn: true,
});
}
}
// If this is not a sub-cycle (it's the top-level invocation), then convert
// the set to an array and remove the first "." from each string
if (!skipReturn) return [...paths].map(p => p.slice(1));
}
// Use:
const obj = {
props: {
abc: {
def: 1,
},
ghi: {
jkl: 2,
},
},
xyz: 3,
};
let someKnownProps = ['props.abc', 'xyz'];
let objectPaths = findObjectPaths(obj);
let hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // true
// An example of all of the paths in the object above:
someKnownProps = [
'props',
'props.abc',
'props.abc.def',
'props.ghi',
'props.ghi.jkl',
'xyz',
];
objectPaths = findObjectPaths(obj);
hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // false
// Finally, comparing the results of inherited vs non-inherited enumeration:
const objWithoutOwnProps = Object.create({
props: {
abc: {
def: 1,
},
ghi: {
jkl: 2,
},
},
xyz: 3,
});
console.log(
'Non-inherited props:',
findObjectPaths(objWithoutOwnProps),
);
console.log(
'Inherited props:',
findObjectPaths(objWithoutOwnProps, {includeInherited: true}),
);
Similar to what Mina said:
let obj = {one: 1, two: 2, three: 3};
let knownKeys = ['one', 'two'];
for (let key in obj) {
if (!knownKeys.includes(key)) {
console.log(key);
}
}

How to get the value of the key present inside an object in Javascript

Below is my sample data from which I want to extract the string present inside ResponseVariations array's object : CriterionExpression
Articles":[
"ResponseVariations":[
"CriterionExpression":"User: internal AND Country: CA"
]
]
Code Snippet:
function getChannel(agent){
const channelInfo = agent.Articles;
channelInfo.forEach((ResponseVariations) => {
if(channelInfo.values(CriterionExpression)!=="DEFAULT_SELECTION")
var iterator = channelInfo.values();
console.log(iterator.next().value);
});
But the above criteria is not fitting well to extract information of those criteria in which the String is not DEFAULT_SELECTION.
Any suggestions how to traverse this array object's value ?
The below code worked in order to fetch the key:
const _dir1 = path.resolve(__dirname, `../`);
const _config1 = JSON.parse(fs.readFileSync(path.resolve(_dir, "./export.4862052732962492282_2.json"), "utf-8"));
const filteredArr = {};
_config1.Articles.forEach(el => {
if (el.ResponseVariations && el.ResponseVariations.length > 1 ) {
el.ResponseVariations && el.ResponseVariations.forEach((rv) => {
if(rv.CriterionExpression !== 'DEFAULT_SELECTION') {
console.log('not default', rv);
filteredArr[rv.CriterionExpression]= rv.Text;
project['data']['supportedChannels'].push(filteredArr);
}
})

Why I can't merge two arrays of objects into one? Error: array is not iterable

I've been working on a scraper. Now I've created two scrapers which are creating one array of objects each.
Now I've managed to scrape data, put them in seperate array, created component parseData where I'm sending these two arrays of objects inside it. And I now want to create a final one array of objects which will be sent to DB for inserting data.
Now here is where I'm trying to merge these two arrays:
const result = []
exports.parseData = (apartments) => {
result.push(apartments);
console.log(result);
};
And this doesn't give me output that I expected. I expected two arrays to be merged not pushed into one. And I understand why is that happening - because of push method
Individual outputs of these two are:
apartments in olxScraper
[
{
id: '41676279',
link: 'www.link.com',
description: 'Description of apartment',
price: 387000,
picture: 'link to picture',
squaremeters: 86,
pricepersquaremeter: 4500
},
{
//same object as before but different data
}
]
apartments in santScraper
[
{
id: '3524',
link: 'www.linkSecondScraper.com',
description: 'Description',
price: 150000,
picture: 'picture',
squaremeters: 55,
pricepersquaremeter: 2727
},
{
//same object as before but different data
}
]
They are the same arrays with same key:value pairs in the objects. Only difference is number of objects inside of a array.
Now What would be proper way to merge these two? I just want one array that contains objects from these two arrays.
Also here is from where arrays are created:
const parsing = require('../data-functions/parseData');
const axios = require('axios');
const cheerio = require('cheerio');
exports.olxScraper = (count) => {
const url = `https://www.olx.ba/pretraga?vrsta=samoprodaja&kategorija=23&sort_order=desc&kanton=9&sacijenom=sacijenom&stranica=${count}`;
const apartments = [];
const getRawData = async () => {
try {
await axios.get(url).then((res) => {
const $ = cheerio.load(res.data);
$('div[id="rezultatipretrage"] > div')
.not('div[class="listitem artikal obicniArtikal i index"]')
.not('div[class="obicniArtikal"]')
.each((index, element) => {
$('span[class="prekrizenacijena"]').remove();
const getLink = $(element)
.find('div[class="naslov"] > a')
.attr('href');
const getDescription = $(element)
.find('div[class="naslov"] > a > p')
.text();
const getPrice = $(element)
.find('div[class="datum"] > span')
.text()
.replace(/\.| ?KM$/g, '')
.replace(' ', '');
const getPicture = $(element)
.find('div[class="slika"] > img')
.attr('src');
apartments[index] = {
id: getLink.substring(27, 35),
link: getLink,
description: getDescription,
price: parseFloat(getPrice),
picture: getPicture,
};
});
const fetchSquaremeters = Promise.all(
apartments.map((item) => {
return axios.get(item.link).then((response) => {
const $ = cheerio.load(response.data);
const getSquaremeters = $('div[class="df2 "]')
.first()
.text()
.replace('m2', '')
.replace(',', '.')
.split('-')[0];
item.squaremeters = parseFloat(getSquaremeters);
item.pricepersquaremeter = Math.round(
parseFloat(item.price) / parseFloat(getSquaremeters)
);
});
})
);
fetchSquaremeters.then(() => {
//console.log(olxApartments);
parsing.parseData(apartments);
});
});
} catch (error) {
console.log(error);
}
};
getRawData();
};
const parsing = require('../data-functions/parseData');
const axios = require('axios');
const cheerio = require('cheerio');
exports.santScraper = (count) => {
const url = `https://www.sant.ba/pretraga/prodaja-1/tip-2/cijena_min-20000/stranica-${count}`;
const apartments= [];
const getRawData = async () => {
try {
await axios.get(url).then((response) => {
const $ = cheerio.load(response.data);
$('div[class="col-xxs-12 col-xss-6 col-xs-6 col-sm-6 col-lg-4"]').each(
(index, element) => {
const getLink = $(element).find('a[class="re-image"]').attr('href');
const getDescription = $(element).find('a[class="title"]').text();
const getPrice = $(element)
.find('div[class="prices"] > h3[class="price"]')
.text()
.replace(/\.| ?KM$/g, '')
.replace(',', '.');
const getPicture = $(element).find('img').attr('data-original');
const getSquaremeters = $(element)
.find('span[class="infoCount"]')
.first()
.text()
.replace(',', '.')
.split('m')[0];
const pricepersquaremeter =
parseFloat(getPrice) / parseFloat(getSquaremeters);
apartments[index] = {
id: getLink.substring(42, 46),
link: getLink,
description: getDescription,
price: Math.round(getPrice),
picture: getPicture,
squaremeters: Math.round(getSquaremeters),
pricepersquaremeter: Math.round(pricepersquaremeter),
};
}
);
parsing.parseData(apartments);
});
} catch (error) {
console.log(console.log(error));
}
};
getRawData();
};
EDIT
Also one problem: I need somehow to tell this function to wait for both arrays to come in, because it first pushes the one array alone, then when second gets in, they are both pushed in.
[
[
{//objects of sant}
]
]
[
[
{//objects of sant}
],
[
{//objects of olx}
]
]
Your parseData function excepts 2 arguments.
exports.parseData = (olxApartments, santApartments) => {
const result = [...olxApartments, ...santApartments];
console.log(result);
};
But in both cases where you call the function, you are only passing it one argument.
parsing.parseData(olxApartments);
parsing.parseData(santApartments);
So you are getting this error because inside the parseData function, santApartments is undefined and therefore not iterable. Both times you are calling the function, you are only passing one argument to it, which is then referenced as olxApartments.
parseData does not store the values of these arguments. If you want to combine olxApartments and santApartments, you need to create an array at a higher scope than this function, and push the relevant data to that array.
Adding info based on questions. The following code might help you:
const apartmentsData = [];
const getOlxApartments = () => {
let olxApartments = [];
olxApartments.push({
id: "41676279",
});
olxApartments.push({
id: "41676280",
});
parseData(olxApartments);
};
const getSantApartments = () => {
let santApartments = [];
santApartments.push({
id: "3524",
});
santApartments.push({
id: "3521",
});
parseData(santApartments);
};
const parseData = (apartments) => {
apartmentsData.push(...apartments);
};
getOlxApartments();
getSantApartments();
console.log(apartmentsData);
// Array(4)
// 0: {id: "41676279"}
// 1: {id: "41676280"}
// 2: {id: "3524"}
// 3: {id: "3521"}

Dynamically get values of object from array

Let's say I have an Object myBook and an array allCategories.
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {
isItScienceFiction: true,
isItManga: false,
isItForKids: false
}
What I want : Loop over categories to check the value of Book, for example, check if "sciencefiction" exists in my Book Object and then check it's value
What I have tried :
1) With indexOf
allCategories.map((category) => {
Object.keys(myBook).indexOf(category)
// Always returns -1 because "sciencefiction" doesn't match with "isItScienceFiction"
});
2) With includes
allCategories.map((category) => {
Object.keys(myBook).includes(category)
// Always returns false because "sciencefiction" doesn't match with "isItScienceFiction"
});
Expected output :
allCategories.map((category) => {
// Example 1 : Returns "sciencefiction" because "isItScienceFiction: true"
// Example 2 : Returns nothing because "isItManga: false"
// Example 3 : Returns nothing because there is not property in myBook with the word "school"
// Example 4 : Returns nothing because there is not property in myBook with the word "art"
// If category match with myBook categories and the value is true then
return (
<p>{category}</p>
);
});
If you need more information, just let me know, I'll edit my question.
You could use filter and find methods to return new array of categories and then use map method to return array of elements.
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {isItScienceFiction: true, isItManga: false, isItForKids: false}
const result = allCategories.filter(cat => {
const key = Object.keys(myBook).find(k => k.slice(4).toLowerCase() === cat);
return myBook[key]
}).map(cat => `<p>${cat}</p>`)
console.log(result)
You can also use reduce instead of filter and map and endsWith method.
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {isItScienceFiction: true,isItManga: false,isItForKids: false}
const result = allCategories.reduce((r, cat) => {
const key = Object.keys(myBook).find(k => k.toLowerCase().endsWith(cat));
if(myBook[key]) r.push(`<p>${cat}</p>`)
return r;
}, [])
console.log(result)
You can use
Object.keys(myBook).forEach(function(key){console.log(myBook[key])})
... place you code instead of console.log. This can do the trick without hard coding and also the best practice.
You should really not keep a number of properties containing booleans. While that might work for 1, 2 or 3 categories, for a few hundred it won't work well. Instead, just store the categories in an array:
const myBook = {
categories: ["sciencefiction", "manga", "kids"],
};
If you got some object with the old structure already, you can easily convert them:
const format = old => {
const categories = [];
if(old.isItScienceFiction)
categories.push("sciencefiction");
if(old.isItManga)
categories.push("manga");
if(old.isItForKids)
categories.push("kids");
return { categories };
};
Now to check wether a book contains a certain category:
const isManga = myBook.categories.includes("manga");
And your rendering is also quite easy now:
myBook.categories.map(it => <p>{it}</p>)
Use Array.filter() and Array.find() with a RegExp to find categories that have matching keys. Use Array.map() to convert the categories to strings/JSX/etc...
const findMatchingCategories = (obj, categories) => {
const keys = Object.keys(obj);
return allCategories
.filter(category => {
const pattern = new RegExp(category, 'i');
return obj[keys.find(c => pattern.test(c))];
})
.map(category => `<p>${category}</p>`);
};
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {
isItScienceFiction: true,
isItManga: false,
isItForKids: false
};
const result = findMatchingCategories(myBook, allCategories);
console.log(result);
You can modify the key names in myBook object for easy lookup like:
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {
isItScienceFiction: true,
isItManga: false,
isItForKids: false
}
const modBook = {}
Object.keys(myBook).map((key) => {
const modKey = key.slice(4).toLowerCase()
modBook[modKey] = myBook[key]
})
const haveCategories = allCategories.map((category) => {
if (modBook[category]) {
return <p>{category}</p>
}
return null
})
console.log(haveCategories)
Converting sciencefiction to isItScienceFiction is not possible and looping all the keys of myBook for every category is not optimal.
But converting isItScienceFiction to sciencefiction is pretty easy, so you can create newMyBook from yourmyBook and use it instead to check.
Creating newMyBook is a one time overhead.
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {isItScienceFiction: true,isItManga: false,isItForKids: false};
const newMyBook = Object.keys(myBook).reduce((a, k) => {
return { ...a, [k.replace('isIt', '').toLowerCase()]: myBook[k] };
}, {});
console.log(
allCategories.filter(category => !!newMyBook[category]).map(category => `<p>${category}</p>`)
);
You can try like this:
const allCategories = ["sciencefiction", "manga", "school", "art"];
const myBook = {
isItScienceFiction: true,
isItManga: false,
isItForKids: false
};
const myBookKeys = Object.keys(myBook);
const result = allCategories.map(category => {
const foundIndex = myBookKeys.findIndex(y => y.toLowerCase().includes(category.toLowerCase()));
if (foundIndex > -1 && myBook[myBookKeys[foundIndex]])
return `<p>${category}</p>`;
});
console.log(result);
You could create a Map for the the categories and keys of object:
const allCategories = ["sciencefiction", "manga", "school", "art"],
myBook = { isItScienceFiction:true, isItManga:false, isItForKids:false }
const map = Object.keys(myBook)
.reduce((r, k) => r.set(k.slice(4).toLowerCase(), k), new Map);
/* map:
{"sciencefiction" => "isItScienceFiction"}
{"manga" => "isItManga"}
{"forkids" => "isItForKids"}
*/
allCategories.forEach(key => {
let keyInObject = map.get(key); // the key name in object
let value = myBook[keyInObject]; // value for the key in object
console.log(key, keyInObject, value)
if(keyInObject && value) {
// do something if has the current key and the value is true
}
})

Categories

Resources