Best way to navigate throught a JSON in Node while validating the path - node.js

I'm trying to get some info out of a API call in Nodejs, structured something like a JSON:
{
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
}
}
I'm not sure about the presence of none of these fields(Json could be incomplete).
Is there a better way to get the value of "name" in JSON.event.event_context.sport.name without an ugly if to not get errors like "cannot get field 'sport' of undefined"?
Currently, I'm doing
if(json.event && json.event.event_context && json.event.event_context.sport) {
return json.event.event_context.sport.name;
}
Is there a better way?
Thank you!

what do you mean by saying "I'm not sure about the presence of none of these fields"?
i don't understand what your'e trying to achieve.

Looks like there is also an interesting package that will allow more conditions on searching json :
https://www.npmjs.com/package/jspath
let getNested = (path, obj) => {
return path.split(".").reduce( getPath, obj);
}
let getPath = (path, key) => {
return (path && path[key]) ? path[key] : null
}
let test = {
"foo": "bar",
"baz": { "one": 1, "two": ["to", "too", "two"] },
"event": { "event_context": { "sport": { "name": "soccer" } } }
}
console.log(getNested("none", test))
console.log(getNested("baz.one", test))
console.log(getNested("baz.two", test))
console.log(getNested("event.event_context.sport.name", test))

You can use lodash get to get a potentially deeply-nested value, and also specify a default in case it doesnt exist.
Example
const _ = require('lodash');
const my_object = {
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
};
_.get(my_object, 'event.event_context.sport.name'); // "Soccer"
_.get(my_object, 'event.event_context.sport.nonExistentField', 'default val'); // "default val"
Article: https://medium.com/#appi2393/lodash-get-or-result-f409e73e018b

You can check by using a function to check object keys like :
function checkProperty(checkObject, checkstring){
if(!checkstring)
return false;
var propertiesKeys = checkstring.split('.');
propertiesKeys.forEach(element => {
if(!checkObject|| !checkObject.hasOwnProperty(element)){
return false;
} else {
checkObject= checkObject[element];
}
})
return true;
};
var objectToCheck = {
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
}
}
if (checkProperty(objectToCheck ,'event.event_context.sport.name'))
console.log('object to find is : ', objectToCheck .event.event_context.sport.name;)

Yeah there are better ways!
For example, you could use lodash's get() method to reach a nested value.
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
But there is also a native solution.
Currently (11.2019) only Babel can handle this.
I am speaking of Optional chaining. It's new in the Ecmascript world.
Why I like it? Look here!
// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;
What happens when db, user, or name is undefined or null? With the optional chaining operator, JavaScript initializes nameLength to undefined instead of throwing an error.
If you are using Babel as a compiler then you could use it now.
Related link: https://v8.dev/features/optional-chaining

Related

Eslint rule is running multiple times

I'm trying to write an eslint rule that enforces making sure the name property is defined on any classes that extend from other Error/Exception named classes (and fixes them).
As far as I can tell, it works in the astexplorer.net individually, but when I'm running it alongside other rules, it ends up getting ran multiple times, so the name property ends up being repeated multiple times in the resulting "fixed" file.
Is there anything in particular I can do to prevent it being run multiple times? I'm assuming what's happening is that it's inserting my name = 'ClassName';, then prettier is needing to reformat the code, which it does, but then maybe it's re-running my rule? I'm not sure.
Rule/fix code shown below. I've tried things like using *fix and yield, but that doesn't seem to help either (see commented code below, based on information in the eslint documentation)
module.exports = {
meta: {
hasSuggestions: true,
type: 'suggestion',
docs: {},
fixable: 'code',
schema: [], // no options,
},
create: function (context) {
return {
ClassDeclaration: function (node) {
const regex = /.*(Error|Exception)$/;
// If the parent/superClass is has "Error" or "Exception" in the name
if (node.superClass && regex.test(node.superClass.name)) {
let name = null;
const className = node.id.name;
// Test class object name
if (!regex.test(className)) {
context.report({
node: node,
message: 'Error extensions must end with "Error" or "Exception".',
});
}
// Find name ClassProperty
node.body.body.some(function (a) {
if (a.type === 'ClassProperty' && a.key.name === 'name') {
name = a.value.value;
return true;
}
});
// Name property is required
if (!name) {
context.report({
node: node,
message: 'Error extensions should have a descriptive name',
fix(fixer) {
return fixer.replaceTextRange(
[node.body.range[0]+1, node.body.range[0]+1],
`name = '${className}';`
);
},
// *fix(fixer) {
// name = className;
// yield fixer.replaceTextRange(
// [node.body.range[0]+1, node.body.range[0]+1],
// `name = '${className}';`
// );
//
// // extend range of the fix to the range of `node.parent`
// yield fixer.insertTextBefore(node.body, '');
// yield fixer.insertTextAfter(node.body, '');
// },
});
}
}
},
};
},
};
Turns out I had the AST Explorer set to the wrong parser, so it was showing me the wrong string name for the ClassProperty node. I should have been using PropertyDefinition instead.

Cypress get text value from an element

I am trying to get a text from an element with Cypress in the first test from the first domain and then type it in the second test in another domain, here is a code
I have to grab code from h4.
I implemented next part of code:
get studentCouponValue() {
return cy.get('h4').then(($span) => {
const couponValue = $span.text();
cy.log(couponValue);
})
}
in logs, I see the correct coupon's value, but when I am trying to type it into the field I get an error
The chain approach doesn't fit my expectation, cause i am going to use it in different tests.
Try this:
get studentCouponValue() {
return cy.get('h4').then(($span) => {
const couponValue = $span.innerText;
cy.log(couponValue);
})
}
i resolved
initStudentCouponValue() {
const self = this;
return cy.get('main > .container-fluid').find('h4').then((span) => {
self.couponValue = span.text();
cy.log('First log '+ self.couponValue);
return new Cypress.Promise((resolve) => {
return resolve(self.couponValue);
});
});
}
getStudentCouponValue() {
return this.couponValue;
}
in the test where we want to use value
let couponValue;
admin.initStudentCouponValue().then(() => {
couponValue = admin.getStudentCouponValue()
});
and later we can use
coupoValue
for inputs

Getting a string from JSON object with args from another object

Say I have a JSON file like this:
{"ABCD":{
"1_99":{"type": 3, "serverPath": "http://some.website.com"}
},
"EFGH":{
"1_00":{"type": 2, "serverPath": "http://another.meme.website/"}
"1_01":{"type": 2, "serverPath": "http://yet.another.website.com/memes"}
},
etc..
}
and I want to reference one of the element in that file:
let appList = require('./config/appList.json');
...
var uri = appList.ABCD.1_99.serverPath;
where element name "ABCD" and "1_99" comes from another object and it's not always the same, say:
var uri = appList. [httpPOSTRequest.app_id] . [httpPOSTRequest.app_ver] . serverPath;
which I wonder if there's any way that can do so please.
You can loop through your appList or you can use other libraries like (https://lodash.com/). I have implemented it with simple loop
let appList = {
"ABCD": {
"1_99": {
"type": 3,
"serverPath": "http://some.website.com"
}
},
"EFGH": {
"1_00": {
"type": 2,
"serverPath": "http://another.meme.website/"
}
}
}
Object.keys(appList).map((d,i)=>{
Object.keys(appList[d]).map((data, index)=>{
console.log(`server path ${i} : ${appList[d][data].serverPath}`);
})
})
You can encapsulate your search properties in a search object, then use this to access your appList object using JavaScript bracket notation.
I've wrapped this up in a getServerPath function which also conveys the intent of the exercise.
We'll return null if we can't find the path in question.
Also, bear in mind, there is a very useful function in the lodash library _.get which can do an awful lot of this stuff for you, I've included an example below.
I hope this helps you!
// Get the required server path given the searchInput object
function getServerPath(appList, searchInput) {
if (!searchInput) return null;
if (!appList[searchInput.property1]) return null;
if (!appList[searchInput.property1][searchInput.property2]) return null;
return appList[searchInput.property1][searchInput.property2].serverPath;
}
// In Node.js we would use require('file.json') to define this.
let appList = {
"ABCD": {
"1_99": {
"type": 3,
"serverPath": "http://some.website.com"
}
},
"EFGH": {
"1_00": {
"type": 2,
"serverPath": "http://another.meme.website/"
}
}
}
// Search object input
let searchInput1 = { property1: "ABCD", property2: "1_99" };
let searchInput2 = { property1: "EFGH", property2: "1_00" };
let searchInput3 = { property1: "DOESNTEXIST", property2: "1_00" };
console.log("Server path 1:", getServerPath(appList, searchInput1));
console.log("Server path 2:", getServerPath(appList, searchInput2));
console.log("Server path 3:", getServerPath(appList, searchInput3));
// Using the very useful lodash _.get
console.log("Server path (using lodash _.get):", _.get(appList, "ABCD.1_99.serverPath"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

Error: wait.for can only be called inside a fiber

I have 2 scipts almost identical with a cascade of function calls nested in a fiber.
This one (parsing Tx in a blockchain) with three calls works perfectly
wait.launchFiber(blockchain)
function blockchain() {
foreach block {
parseBlock (blockIndex)
}
}
function parseBlock(blockIndex) {
foreach Tx in block {
parseTx(txHash)
}
}
function parseTx (txHash) {
if ( txHashInDB(txHash) ) {
do something
}
}
function txHashInDB (txHash) {
var theTx = wait.forMethod(Tx, 'findOne', {'hash': txHash});
return (theTx) ? true : false;
}
Then I have to do something similar with the mempool. In this case I don't have blocks, only transactions, so I have only 2 calls and I get this error message:
Error: wait.for can only be called inside a fiber
wait.launchFiber(watchMempool);
function watchMempool() {
web3.eth.filter('pending', function (error, txHash) {
parseTx(txHash);
});
}
function parseTx (txHash) {
if ( txHashInDB(txHash) ) {
do something
}
}
function txHashInDB (txHash) {
var theTx = wait.forMethod(Tx, 'findOne', {'hash': txHash});
return (theTx) ? true : false;
}
I don't understand what the problem is. Those two scripts have the same structure !
I think for array functions like map or filter you need to use the wait.parallel extensions, i.e. in your case something like:
function watchMempool() {
wait.parallel.filter(web3.eth, parseTx);
}
(Note: I'm just assuming web3.eth is an array; if not, you should probably add a bit more context to your question, or try to boil down the problem to a more generic example).

YUI, instantiable module that is not a widget?

If I want a module that is instantiable, let say, a module that handles storing preferences in a subcookies, and i want the main cookie to be configurable, but i don't want it to be a widget... what patterns should i use with YUI?
the end code should be something:
Y.use('my-pref-manager', function(Y){
var A = Y.my-pref-manager.prefStore('A"),
B = Y.my-pref-manager.prefStore('B");
// A and B are now loaded with the contents of cookies A and B, if they exist
A.set('xy', 123 );
});
So far i either found patterns that create widgets within my-module or i have to use methods directly in my-method which will be globals and lack initializers, etc.
There is a bunch of ways of doing this. You could do it using Y.Base.create, like below. The code might not be production ready, or even working properly, but hopefully it answers how you can create a module without it being a Widget.
The code below creates a module that extends Y.Base. This let us use Attributes and other cool things. Check the doc for Y.Base.
YUI.add('my-pref-manager', function (Y) {
var PrefManager = Y.Base.create('myPrefManager', Y.Base, [], {
initializer: function () {
this.after('prefsChange', this.changePref);
},
changePref: function (e) {
Y.Cookie.setSub(this.get('prefStore'), e.subAttrName, this.get(e.subAttrName));
},
setPref: function (name, val) {
this.set('prefs.'+name, val);
},
getPref: function (name) {
return this.get('prefs.'+name);
}
}, {
ATTRS: {
prefStore: {
value: null,
setter: function (val) {
return Y.Cookie.set(val, val);
}
},
prefs: {
value: {}
}
}
});
Y.namespace('My').PrefManager = PrefManager;
}, '0.0.1', {
requires: ['base', 'cookie']
});
YUI().use('my-pref-manager', function (Y) {
var A = new Y.My.PrefManager({prefStore: 'userPrefs'}),
B = new Y.My.PrefManager({prefStore: 'displayPrefs'});
A.setPref('x', 3);
A.setPref('y', 54);
B.setPref('tty', 7);
console.log(A.getPref('x')); // 3
});
Try it out: http://jsfiddle.net/B62nu/

Resources