Access second level child in Json based on a variable - javascript

I'm currently working on a configuration service in my Angular 2 application, my current concern is about the utilization of eval in my code to retrieve a value in my configuration.
Here's a sample of my configuration file:
{
"application": {
"environment": "dev",
"displayMenu": false
},
"error": {
"title": "Title Error",
"message": "Error message"
}
}
I'm retrieving this JSON with a simple HTTP request, but now I would like to access an element with my get method defined like this:
get(key: any) {
return (eval("this._config." + key));
}
As you can see, there is an eval in my code that I would like to avoid. I'm forced to use eval to allow the developer to do .get('application.environment') and actually I don't find any others an easy possibility.
The only other way that I could see is to split the key on a "." and retrieve the right element in my JSON like it was a simple array. But with this solution, I would be stuck to one depth only.

You could use an array of your object keys you wish to view, then reduce that array returning the key of the object. If you wish to have a string as the object accessors you can easily use string.split('.') to create the array you can reduce.
const data = {
"application": {
"environment": "dev",
"displayMenu": false
},
"error": {
"title": "Title Error",
"message": "Error message",
"deeper": {
"evenDeeper": "You can get to any level"
}
}
}
const path = (keys, obj) => {
return keys.reduce((obj, key) => {
return typeof obj !== 'undefined'
? obj[key]
: void 0
}, obj)
}
console.log(
path(['application', 'environment'], data)
)
console.log(
path('error.message'.split('.'), data) // move the split inside the path function
)
console.log(
path(['error', 'deeper', 'evenDeeper'], data)
)
console.log(
path(['error', 'fail', 'damn'], data) // fail safe
)
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>

Related

Find nested object that contains a value

I have an object containing multiple other objects, inside these nested objects is an array containing multiple objects, each with a uid. I'm trying to loop over the objects and find the object that contains a particular uid.
My data looks like this
const data = {
"3c5671fde44f44f9ad59d59eb810d87e": {
"heading": "Heading 1",
"items": [
{
"content": {
"uid": "4fcd5f4af7a448d48463d4e0a11297d9"
}
},
{
"content": {
"uid": "31f975440a0a431592e127b2891cd142"
}
}
]
},
"ea80e8315b554c9bb40958a6cacf4b0c": {
"heading": "Heading 2",
"items": [
{
"content": {
"uid": "d6de8db4c2a74da6915a44d3964277d6"
}
}
]
}
}
The uid I want to search for is d6de8db4c2a74da6915a44d3964277d6 when found I want to return it's parent object so I can access the heading property.
Current code looks like this but it doesn't work, it's been a long day so I'm likely missing something really simple.
const currentUid = "d6de8db4c2a74da6915a44d3964277d6";
const currentHeading = Object.keys(data).forEach(section => {
return data[section].items.filter(item => {
return item.content.uid === currentUid;
});
});
When debugging it successfully evaluates to true when it finds the correct uid, it just doesn't return anything.
Any help welcome!
forEach is meant just for looping the array, use find to find the key of your object from Object.keys(data). And inside the callback use some instead of filter to check the existance. This solution will result in either the key of the object or null. To get the object, just check that a key is returned and then use that key to get the object:
const currentHeadingKey = Object.keys(data).find(section => {
return data[section].items.some(item => {
return item.content.uid === currentUid;
});
});
const currentHeading = currentHeadingKey != null ? data[currentHeadingKey] : null;
currentHeading is now either the whole object if found, null otherwise. You can access the heading property of that object.
Note: Since the callbacks of both find and some have only one statement in them, you can use the implicit return of arrow function to shorten the code:
const currentHeadingKey = Object.keys(data).find(section =>
data[section].items.some(item => item.content.uid === currentUid)
);
Consider using the Object.values() method instead, to extract the "parent heading" for the supplied uid.
Taking this approach, you can iterate the values of data, and filter those section values that contain items matching the uid. In the answer below, this is done via:
return items.some(item => item.content.uid === currentUid)
Finally, you can map() the filtered sections to acquire the corresponding heading(s) of section with matching uid items:
function findHeading(data, uid) {
return Object.values(data).filter(section => {
// Find any item with matching uid in this section, filter this section accordingly
return section.items.some(item => item.content.uid === currentUid)
})
.map(section => {
// Map heading from any sections matching item uid
return section.heading
});
}
const data = {
"3c5671fde44f44f9ad59d59eb810d87e": {"heading": "Heading 1","items": [{"content": {"uid": "4fcd5f4af7a448d48463d4e0a11297d9"}},{"content": {"uid": "31f975440a0a431592e127b2891cd142"}}]},"ea80e8315b554c9bb40958a6cacf4b0c": {"heading": "Heading 2","items": [{"content": {"uid": "d6de8db4c2a74da6915a44d3964277d6"}}]}
}
const currentUid = "d6de8db4c2a74da6915a44d3964277d6";
console.log(findHeading(data, currentUid))

How to write a postman test to compare the response json against another json?

I have the below json response after running a postMan test of a Rest API:
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
Now I would like to compare the above json against a predefined json. Say, its the same as above.
How can I compare two jsons via the Postman test?
I had a similar problem to solve except that my JSON also contained an array of objects. I used the following technique that can be modified to deal with the simple array of strings in your question.I created an array of global functions called "assert", which contained helper functions such as "areEqual" and "areArraysOfObjectsEqual" and saved these under the "Tests" tab at a top folder level of my tests.
assert = {
areEqual: (actual, expected, objectName) => {
pm.test(`Actual ${objectName} '` + actual + `' matches Expected ${objectName} '` + expected + `'`, () => {
pm.expect(_.isEqual(actual, expected)).to.be.true;
});
},
areArraysOfObjectsEqual: (actual, expected, objectName) => {
if (!_.isEqual(actual, expected)) {
// Arrays are not equal so report what the differences are
for (var indexItem = 0; indexItem < expected.length; indexItem++) {
assert.compareArrayObject(actual[indexItem], expected[indexItem], objectName);
}
}
else
{
// This fake test will always pass and is just here for displaying output to highlight that the array has been verified as part of the test run
pm.test(`actual '${objectName}' array matches expected '${objectName}' array`);
}
},
compareArrayObject: (actualObject, expectedObject, objectName) => {
for (var key in expectedObject) {
if (expectedObject.hasOwnProperty(key)) {
assert.areEqual(expectedObject[key], actualObject[key], objectName + " - " + key);
}
}
}
};
Your "Pre-request Script" for a test would set your expected object
const expectedResponse =
{
"id": "3726b0d7-b449-4088-8dd0-74ece139f2bf",
"array": [
{
"item": "ABC",
"value": 1
},
{
"item": "XYZ",
"value": 2
}
]
};
pm.globals.set("expectedResponse", expectedResponse);
Your Test would test each item individually or at the array level like so:
const actualResponse = JSON.parse(responseBody);
const expectedResponse = pm.globals.get("expectedResponse");
assert.areEqual(
actualResponse.id,
expectedResponse.id,
"id");
assert.areArraysOfObjectsEqual(
actualResponse.myArray,
expectedResponse.myArray,
"myArrayName");
This technique will give nice "property name actual value matches expected value" output and works with arrays of objects being part of the JSON being compared.
Update:
To test your array of strings "GlossSeeAlso", simply call the supplied global helper method in any of your tests like so:
assert.compareArrayObject(
actualResponse.glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso,
expectedResponse.glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso,
"glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso");
Primitive types in JSON key value pairs can be tested like so:
assert.areEqual(
actualResponse.glossary.title,
expectedResponse.glossary.title,
"glossary.title");
I got it after a while. Add test into your request and use Runner to run all your requests in the collection.
Postman info: Version 7.10.0 for Mac.
Test scripts:
pm.test("Your test name", function () {
var jsonData = pm.response.json();
pm.expect(jsonData).to.eql({
"key1": "value1",
"key2": 100
});
});
You can paste this code into your collection or single request tests tab.
What this code do is to save the request into a global variable with a key for that request. You can change your enviroment and hit the same request and if the response are different the test will fail.
const responseKey = [pm.info.requestName, 'response'].join('/');
let res = '';
try {
res = JSON.stringify(pm.response.json());
} catch(e) {
res = pm.response.text();
}
if (!pm.globals.has(responseKey)) {
pm.globals.set(responseKey, res);
} else {
pm.test(responseKey, function () {
const response = pm.globals.get(responseKey);
pm.globals.unset(responseKey);
try {
const data = pm.response.json();
pm.expect(JSON.stringify(data)).to.eql(response);
} catch(e) {
const data = pm.response.text();
pm.expect(data).to.eql(response);
}
});
}
Hope this help.
You can write javascript code inside Tests tab of Postman. Just write simple code to compare and check result in Tests.
var serverData = JSON.parse(responseBody);
var JSONtoCompare = {}; //set your predefined JSON here.
tests["Body is correct"] = serverData === JSONtoCompare;
Looks like the same question asked at POSTMAN: Comparing object Environment variable with response's object which also lists a solution that works, which is to use JSON.stringify() to turn the objects into strings and then compare the strings.
Came across this issue when migrating from a legacy API to a new one and wanting to assert the new API is exactly the same as the old under different scenarios
For context this clones params of the original get request to the legacy endpoint and validates both match up
LEGACY_API_URL should be defined in the environment and the Request is going to the new API
const { Url } = require('postman-collection');
// Setup the URL for the Legacy API
const legacyRequestUrl = new Url({ host: pm.variables.replaceIn("http://{{LEGACY_API_HOST}}/blah")});
// Add All Parameters From the Source Query
legacyRequestUrl.addQueryParams(pm.request.url.query.all());
// Log out the URL For Debugging Purposes
console.log("URL", legacyRequestUrl.toString());
pm.sendRequest(legacyRequestUrl.toString(), function (err, response) {
pm.test('New API Response Matches Legacy API Response', function () {
// Log Out Responses for Debugging Purposes
console.log("New API Response", pm.response.json())
console.log("Legacy API Response", response.json())
// Assert Both Responses are Equal
pm.expect(_.isEqual(pm.response.json(), response.json())).to.be.true
});
});
Link to an example collection
https://www.getpostman.com/collections/4ff9953237c0ab1bce99
Write JavaScript code under 'Tests' section. Refer below link for more info.
Click Here

How to check in NodeJS if json file already got specific data

I'm trying to make a CRUD operations with NodeJS. But i don't know how to check if JSON file already got the specific part and then update only needed object and not overwrite the other data or add the new data if there is no record of it.
this is what i have so far ( right now the insert operation overwrites the whole file and leaves it with only inserted data ) :
JSON :
JSON:
{
"id": 1,
"name": "Bill",
},
{
"id": 2,
"name": "Steve",
},
Code :
var operation = POST.operation; // POST request comes with operation = update/insert/delete
if (operation == 'insert') {
fs.readFile("data.json", "utf8", function (err) {
var updateData = {
id: POST.id,
name: POST.name,
}
var newUser = JSON.stringify(updateData);
fs.writeFile("data.json", newUsers, "utf8");
console.log(err);
})
}
else if (operation == 'update') {
fs.readFile("data.json", "utf8", function (err) {
})
}
else if (operation == 'delete') {
fs.readFile("data.json", "utf8", function (err) {
})
}
else
console.log("Operation is not supported");
The most examples that i've found were with Database and Express.js. So they didn' really help much.
Sorry , i'm a newbie.
So first off, that is not valid JSON (unless this is only part of the file). You will need to wrap that whole list into an array for it to be valid JSON (You can check if your JSON is valid here)
If you go with a structure like this:
[
{
"id": 1,
"name": "Bill",
},
{
"id": 2,
"name": "Steve",
},
]
I think the easiest way to check if the ID already exists will be to read the JSON in as an array and check if the ID has already been assigned. Something like this:
var json = require('/path/to/data.json'); // Your object array
// In the if (operation == 'insert') block
var hadId = json.some(function (obj) {
return obj.id === POST.ID;
});
if (hasId) {
// Do something with duplicate
}
json.push({
id: POST.id,
name: POST.name
});
// Write the whole JSON array back to file
More on the array.some function
So basically you will be keeping the whole JSON file in memory (in the json array) and when you make a change to the array, you write the whole updated list back to file.
You may run into problems with this if that array gets very large, but at that point I would recommend looking into using a database. Mongo (all be it not the greatest DB in the world) is fairly easy to get setup and to integrate with JavaScript while playing and experimenting.
I hope this helps!
Good luck

MongoDB/Mongoose Query Builder

I am trying to build a query builder which will allow me to filter the data based on the parameters entered by user. My Data Model is like so:
{
"_id": {
"$oid": "871287215784812"
},
"tags": [
"school",
"book",
"bag",
"headphone",
"appliance"
],
"consultingDays": 57,
"client": "someOne",
"subSector": "something",
"region": "UK",
"__v": 0
}
Currently my Query Builder looks like this:
app.post('/user/test',function(req, res) {
var query = {};
//QUERY NO.1 - This works perfectly
if (req.body.region){
query.region = req.body.region
console.log(query.region)
}
// QUERY NO.2 - This works perfectly
if (req.body.subSector){
query.subSector = req.body.subSector
}
Project.find(query, function(err, project){
if (err){
res.send(err);
}
console.log(project);
res.json(project);
});
});
My Question:
I want to create a query which will take input from user and parse the "tags" array and return the required JSON.
For example:
If the user requests an object which contains "school", "book", "bag" it will return the object as seen my data model above. But if the user requests an object with "school", "book", "ninja Warrior" it won't return any data as no object within the database contain all those 3 strings.
What I have tried:
I have tried the following
if (req.body.sol){
query.solutions = {"tags" : {$in: [req.body.sol]}}
}
OR
if (req.body.sol){
query.solutions = {$elemMatch:{tags: req.body.sol}}
}
OR
if (req.body.sol){
query.solutions = { tags: { $all: [req.body.sol]}}
}
The requests were sent like so and they returned an empty array:
Also the issue is that the user will get dropdown options. For example he/she might get 3 dropdown boxes. Each dropdown box will display all the five options in the tags array. The user will select a value for each dropdown box. And then filter the result. Because there might be an object within the database that contains "book", "bag", "shoes" within the tags array. The user can select any combination of those five keywords in the tags array
Does anyone know how I can fix this?
You need to send an array as sol so in Postman you should change sol with sol[0], sol[1], etc.. Then use this:
if (req.body.sol){
query.solutions = {"tags" : {$in: req.body.sol}}
}
Without the [] because req.body.sol is an array yet.
I have implemented a simple query build for nested objects:
const checkObject = (object) => {
let key;
const status = Object.entries(object).some(([objectKey, objectValue]) => {
if (typeof objectValue === "object" && objectValue !== null) {
key = objectKey;
return true;
}
return false;
});
return { status, key };
};
const queryBuilder = (input) => {
// Array verification not implemented
let output = {};
_.each(input, (value, key) => {
if (typeof value === "object" && value !== null) {
_.each(value, (nestedValue, nestedKey) => {
output[`${[key, nestedKey].join(".")}`] = nestedValue;
});
} else {
output[key] = value;
}
});
const cacheCheckObject = checkObject(output);
if (cacheCheckObject.status)
return { ..._.omit(output, cacheCheckObject.key), ...queryBuilder(output) };
return output;
};
I have not implemented array, but with some small work you can do it work. The same for Mongo operators. The complete example can be seen on Gist.

MongoDB updated object with item remove not saving

I'm using Angular Fullstack for an web app.
I'm posting my data by $http.post() my object:
{ title: "Some title", tags: ["tag1", "tag2", "tag3"] }
When I edit my object and try to $http.put() for example:
{ title: "Some title", tags: ["tag1"] }
In console I get HTTP PUT 200 but when I refresh the page I still recive the object with all 3 tags.
This is how I save in the MongoDB:
exports.update = function(req, res) {
if (req.body._id) {
delete req.body._id;
}
Question.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(saveUpdates(req.body))
.then(responseWithResult(res))
.catch(handleError(res));
};
function saveUpdates(updates) {
return function(entity) {
var data = _.merge(entity.toJSON(), updates);
var updated = _.extend(entity, data);
return updated.saveAsync()
.spread(function(updated) {
return updated;
});
};
}
Can someone explain how to save the object with removed items?
What I'm doing wrong?
This is pretty bad practice to use things like _.merge or _.extend in client ( meaning your nodejs client to database and not browser ) code after retrieving from the database. Also notably _.merge is the problem here as it is not going to "take away" things, but rather "augment" what is already there with the information you have provided. Not what you want here, but there is also a better way.
You should simply using "atomic operators" like $set to do this instead:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$set": { "tags": req.body.tags } },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
You also really should be targeting your endpoints and not having a "generic" object write. So the obove would be specically targeted at "PUT" for related "tags" only and not touch other fields in the object.
If you really must throw a whole object at it and expect an update from all the content, then use a helper to fix the update statement correctly:
function dotNotate(obj,target,prefix) {
target = target || {},
prefix = prefix || "";
Object.keys(obj).forEach(function(key) {
if ( typeof(obj[key]) === "object" ) {
dotNotate(obj[key],target,prefix + key + ".");
} else {
return target[prefix + key] = obj[key];
}
});
return target;
}
var update = { "$set": dotNotate(req.body) };
Question.findByIdAndUpdateAsync(
req.params.id,
update,
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
Which will correctly structure not matter what the object you throw at it.
Though in this case then probably just directly is good enough:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$set": req.body },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
There are other approaches with atomic operators that you could also fit into your logic for handling. But it is best considered that you do these per element, being at least root document properties and things like arrays treated separately as a child.
All the atomic operations interact with the document "in the database" and "as is at modification". Pulling data from the database, modifiying it, then saving back offers no such guarnatees that the data has not already been changed and that you just may be overwriting other changes already comitted.
I truth your "browser client" should have been aware that the "tags" array had the other two entries and then your "modify request" should simply be to $pull the entries to be removed from the array, like so:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$pull": { "tags": { "$in": ["tag2", "tag3"] } } },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
And then, "regardless" of the current state of the document on the server when modified, those changes would be the only ones made. So if something else modified at added "tag4", and the client had yet to get the noficiation of such a change before the modification was sent, then the return response would include that as well and everything would be in sync.
Learn the update modifiers of MongoDB, as they will serve you well.

Categories

Resources