URI templates: Is there an rfc-6570 implementation in javascript? - javascript

I am using node and express. To register a controller I call:
app.get('/user/:id', function (req, res) {...});
But I would like to do it the rfc-6570 way:
app.get('/user/{id}', function (req, res) {...});
I googled just an implementation in python on google code, but found nothing (except the dead link on google code to http://www.snellspace.com/wp/?p=831) for JavaScript.
URI templating in general is not so easy as it looks on the first sight. Have a look on the examples in the RFC.
PS: I will need the URI templates on the client, too.

I've been cleaning up the implementations list at http://code.google.com/p/uri-templates/wiki/Implementations - there is a JS one at https://github.com/marc-portier/uri-templates but I'm not sure of whether it implements the RFC, nor of what its quality is.
Note that we've started publishing tests here:
https://github.com/uri-templates/uritemplate-test
So if you want to check it, you could start there.

As of June 2014, these JavaScript implementations seem most complete (Level 4 of the spec) and tested. All three also support both the browser and node.js.
fxa/uritemplate-js: npm install uritemplate
geraintluff/uri-templates (does also variable extraction aka de-substitution): npm install uri-templates
URI.js includes URI-Templates: npm install URIjs or bower install uri.js

Regarding the express router part I would recommend to use your uri templates within a hyperschema (read more) ...
Then you could also benefit from regex for your router which express.js supports.
Regarding resolving the parameters you need an RFC 6570 implementation like https://github.com/geraintluff/uri-templates ...
Here is some .js code to illustrate the rewriting of a hyperschema
USING RFC 6570 to convert it to an express js router:
var hyperschema = {
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"links": [
{
"href": "{/id}{/ooo*}{#q}",
"method": "GET",
"rel": "self",
"schema": {
"type": "object",
"properties": {
"params": {
"type": "object",
"properties": {
"id": {"$ref": "#/definitions/id"}
},
"additionalProperties": false
}
},
"additionalProperties": true
}
}
],
"definitions": {
"id": {
"type": "string",
"pattern": "[a-z]{0,3}"
}
}
}
var deref = require('json-schema-deref');
var tv4 = require('tv4');
var url = require('url');
var rql = require('rql/parser');
// DOJO lang AND _
function getDottedProperty(object, parts, create) {
var key;
var i = 0;
while (object && (key = parts[i++])) {
if (typeof object !== 'object') {
return undefined;
}
object = key in object ? object[key] : (create ? object[key] = {} : undefined);
}
return object;
}
function getProperty(object, propertyName, create) {
return getDottedProperty(object, propertyName.split('.'), create);
}
function _rEscape(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function getPattern(k, ldo, customCat) {
// ...* = explode = array
// ...: = maxLength
var key = ((k.slice(-1) === '*') ? k.slice(0,-1) : k).split(':')[0];
var cat = (customCat) ? customCat : 'params'; // becomes default of customCat in TS
var pattern = '';
if (typeof ldo === 'object' && ldo.hasOwnProperty('schema')) {
var res = getProperty(ldo.schema, ['properties',cat,'properties',key,'pattern'].join('.'));
if (res) {
console.log(['properties',cat,'properties',key,'pattern'].join('.'),res);
return ['(',res,')'].join('');
}
}
return pattern;
}
function ldoToRouter(ldo) {
var expression = ldo.href.replace(/(\{\+)/g, '{') // encoding
.replace(/(\{\?.*\})/g, '') // query
.replace(/\{[#]([^}]*)\}/g, function(_, arg) {
// crosshatch
//console.log(arg);
return ['(?:[/]*)?#:',arg,getPattern(arg,ldo,'anchor')].join('');
})
.replace(/\{([./])?([^}]*)\}/g, function(_, op, arg) {
// path seperator
//console.log(op, '::', arg, '::', ldo.schema);
return [op,':',arg,getPattern(arg,ldo)].join('');
});
return {method: ldo.method.toLowerCase(), args:[expression]};
}
deref(hyperschema, function(err, fullSchema) {
console.log('deref hyperschema:',JSON.stringify(fullSchema));
var router = fullSchema.links.map(ldoToRouter);
console.log('router:',JSON.stringify(router));
});

Related

Multiple paramater based routes for an API Endoint in an Azure Function

I have a simple Function App in Azure that returns data in JSON.
https://myurl.com/api/symbolcountry/{id}?
It takes in a single id for a parameter. It works well.
However I'd like to parameterize part of the url
https://myurl.com/api/{endpoint}/{id}?
I've never messed around with javascript and am going a bit nuts trying to figure this one out.
function.json file:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
],
"route": "symbolcountry/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
index.js file:
module.exports = function (context, req) {
const method = req.method.toLowerCase();
var payload = null;
var entity = "";
switch(method) {
case "get":
if (req.params.id) {
entity = "symbolcountry"
payload = { "SymbolID": req.params.id};
}
else {
entity = "symbols"
}
break;
}
}
Tried adding paramters in function.json to no avail.
If you want to access the params passed in the URL instead of params use the key work query
Your code will look like this:
module.exports = function (context, req) {
const metthod = req.method.toLowerCase();
var payload = null;
var entity = "";
switch(metthod)
{
case "get" : if(req.query.id)
{
entity="symbolcountry";
payload = {
"SymbolId":req.query.id
}
}
break;
}
context.res = {
body = payload
}
The above code will return the payload
output :
If you want to parameters through routing, you can refer this article by JOE GATT

Swagger-codegen-js not finding not translating the response schema defined on the api endpoint

Summary
I am using swagger-codegen-js to generate typescript files according to the swagger.json defined by an external api.
packages used
"swagger-js-codegen": "^1.12.0",
Alleged Problem
The return type on the method listMovies on the generated TS file is simply Promise<Request.Response> and not Promise<Array<Movie>>, i did expect it to be array of movies as the response clearly state the schema and thought/assumed that would be translated.
Given a json along the lines of the following, the
"/movies": {
"get": {
"description": "Lists movies",
"operationId": "listMovies",
"responses": {
"200": {
"description": "Movie",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Movie"
}
}
},
"default": {
"$ref": "#/responses/genericError"
}
}
},
"definitions": {
"Movie": {
"description": "something",
"type": "object",
"properties": {
"archivedAt": {
"description": "When the movie was archived",
"type": "string",
"format": "nullable-date-time",
"x-go-name": "ArchivedAt",
"readOnly": true
}
}
}
Generated TS Method
/**
* Lists movies
* #method
* #name Api#listMovies
*/
listMovies(parameters: {
$queryParameters ? : any,
$domain ? : string
}): Promise <request.Response> {
const domain = parameters.$domain ? parameters.$domain : this.domain;
let path = '/movies';
.
.
.
this.request('GET', domain + path, body, headers, queryParameters, form, reject, resolve);
});
}
The script i use to generate the above ts file is straight from the github sample
const generateTSFilesUsingSwaggerJsCodegen = function () {
var fs = require('fs');
var CodeGen = require('swagger-js-codegen').CodeGen;
var file = 'build/sample.json';
var swagger = JSON.parse(fs.readFileSync(file, 'UTF-8'));
var tsSourceCode = CodeGen.getTypescriptCode({ className: 'Api', swagger: swagger, imports: ['../../typings/tsd.d.ts'] });
fs.writeFileSync('src/api/api.ts', tsSourceCode)
}
Am i missing something on the wire up/options passed or is this the expected generated file given the json file and that i need to write custom script to get what I want?
It is only possible to edit the codegen to change this.
But you can just use the body of the return value
<restMethod>(<paramters>).then(respose: <request.Response>) {
let responseObject: Array<ListMovies> = response.body as Array<ListMovies>;
...
}
If you want to adapt the codegen, pull it from git and change the following files:
lib/codegen.js:
var getViewForSwagger2 = function(opts, type){
var swagger = opts.swagger;
var methods = [];
var authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND'];
var data = {
isNode: type === 'node' || type === 'react',
isES6: opts.isES6 || type === 'react',
description: swagger.info.description,
isSecure: swagger.securityDefinitions !== undefined,
moduleName: opts.moduleName,
className: opts.className,
imports: opts.imports,
domain: (swagger.schemes && swagger.schemes.length > 0 && swagger.host && swagger.basePath) ? swagger.schemes[0] + '://' + swagger.host + swagger.basePath.replace(/\/+$/g,'') : '',
methods: [],
definitions: []
};
_.forEach(swagger.definitions, function(definition, name){
data.definitions.push({
name: name,
description: definition.description,
tsType: ts.convertType(definition, swagger)
});
});
the last _.forEach is moved from the bottom of the method to here.
var method = {
path: path,
className: opts.className,
methodName: methodName,
method: M,
isGET: M === 'GET',
isPOST: M === 'POST',
summary: op.description || op.summary,
externalDocs: op.externalDocs,
isSecure: swagger.security !== undefined || op.security !== undefined,
isSecureToken: secureTypes.indexOf('oauth2') !== -1,
isSecureApiKey: secureTypes.indexOf('apiKey') !== -1,
isSecureBasic: secureTypes.indexOf('basic') !== -1,
parameters: [],
responseSchema: {},
headers: []
};
if (op.responses && op.responses["200"]) {
method.responseSchema = ts.convertType(op.responses["200"], swagger);
} else {
method.responseSchema = {
"tsType": "any"
}
}
this is starting at line 102. add responseSchema to method and append the if / else
templates/typescript-class.mustache (line 68)
resolve(response.body);
templates/typescript-method.mustache (line 69)
}): Promise<{{#responseSchema.isRef}}{{responseSchema.target}}{{/responseSchema.isRef}}{{^responseSchema.isRef}}{{responseSchema.tsType}}{{/responseSchema.isRef}}{{#responseSchema.isArray}}<{{responseSchema.elementType.target}}>{{/responseSchema.isArray}}> {
That only works for simple types, Object types and arrays of simple / object types. I did not implement the enum types.
That seems like the expected behaviour.
The TypeScript Mustache template outputs a fixed value:
...
}): Promise<request.Response> {
...

MongoDB retrieve all keys with Node.js [duplicate]

I'd like to get the names of all the keys in a MongoDB collection.
For example, from this:
db.things.insert( { type : ['dog', 'cat'] } );
db.things.insert( { egg : ['cat'] } );
db.things.insert( { type : [] } );
db.things.insert( { hello : [] } );
I'd like to get the unique keys:
type, egg, hello
You could do this with MapReduce:
mr = db.runCommand({
"mapreduce" : "my_collection",
"map" : function() {
for (var key in this) { emit(key, null); }
},
"reduce" : function(key, stuff) { return null; },
"out": "my_collection" + "_keys"
})
Then run distinct on the resulting collection so as to find all the keys:
db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
With Kristina's answer as inspiration, I created an open source tool called Variety which does exactly this: https://github.com/variety/variety
You can use aggregation with the new $objectToArray aggregation operator in version 3.4.4 to convert all top key-value pairs into document arrays, followed by $unwind and $group with $addToSet to get distinct keys across the entire collection. (Use $$ROOT for referencing the top level document.)
db.things.aggregate([
{"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
{"$unwind":"$arrayofkeyvalue"},
{"$group":{"_id":null,"allkeys":{"$addToSet":"$arrayofkeyvalue.k"}}}
])
You can use the following query for getting keys in a single document.
db.things.aggregate([
{"$match":{_id: "<<ID>>"}}, /* Replace with the document's ID */
{"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
{"$project":{"keys":"$arrayofkeyvalue.k"}}
])
A cleaned up and reusable solution using pymongo:
from pymongo import MongoClient
from bson import Code
def get_keys(db, collection):
client = MongoClient()
db = client[db]
map = Code("function() { for (var key in this) { emit(key, null); } }")
reduce = Code("function(key, stuff) { return null; }")
result = db[collection].map_reduce(map, reduce, "myresults")
return result.distinct('_id')
Usage:
get_keys('dbname', 'collection')
>> ['key1', 'key2', ... ]
If your target collection is not too large, you can try this under mongo shell client:
var allKeys = {};
db.YOURCOLLECTION.find().forEach(function(doc){Object.keys(doc).forEach(function(key){allKeys[key]=1})});
allKeys;
If you are using mongodb 3.4.4 and above then you can use below aggregation using $objectToArray and $group aggregation
db.collection.aggregate([
{ "$project": {
"data": { "$objectToArray": "$$ROOT" }
}},
{ "$project": { "data": "$data.k" }},
{ "$unwind": "$data" },
{ "$group": {
"_id": null,
"keys": { "$addToSet": "$data" }
}}
])
Here is the working example
Try this:
doc=db.thinks.findOne();
for (key in doc) print(key);
Using python. Returns the set of all top-level keys in the collection:
#Using pymongo and connection named 'db'
reduce(
lambda all_keys, rec_keys: all_keys | set(rec_keys),
map(lambda d: d.keys(), db.things.find()),
set()
)
Here is the sample worked in Python:
This sample returns the results inline.
from pymongo import MongoClient
from bson.code import Code
mapper = Code("""
function() {
for (var key in this) { emit(key, null); }
}
""")
reducer = Code("""
function(key, stuff) { return null; }
""")
distinctThingFields = db.things.map_reduce(mapper, reducer
, out = {'inline' : 1}
, full_response = True)
## do something with distinctThingFields['results']
I am surprise, no one here has ans by using simple javascript and Set logic to automatically filter the duplicates values, simple example on mongo shellas below:
var allKeys = new Set()
db.collectionName.find().forEach( function (o) {for (key in o ) allKeys.add(key)})
for(let key of allKeys) print(key)
This will print all possible unique keys in the collection name: collectionName.
I think the best way do this as mentioned here is in mongod 3.4.4+ but without using the $unwind operator and using only two stages in the pipeline. Instead we can use the $mergeObjects and $objectToArray operators.
In the $group stage, we use the $mergeObjects operator to return a single document where key/value are from all documents in the collection.
Then comes the $project where we use $map and $objectToArray to return the keys.
let allTopLevelKeys = [
{
"$group": {
"_id": null,
"array": {
"$mergeObjects": "$$ROOT"
}
}
},
{
"$project": {
"keys": {
"$map": {
"input": { "$objectToArray": "$array" },
"in": "$$this.k"
}
}
}
}
];
Now if we have a nested documents and want to get the keys as well, this is doable. For simplicity, let consider a document with simple embedded document that look like this:
{field1: {field2: "abc"}, field3: "def"}
{field1: {field3: "abc"}, field4: "def"}
The following pipeline yield all keys (field1, field2, field3, field4).
let allFistSecondLevelKeys = [
{
"$group": {
"_id": null,
"array": {
"$mergeObjects": "$$ROOT"
}
}
},
{
"$project": {
"keys": {
"$setUnion": [
{
"$map": {
"input": {
"$reduce": {
"input": {
"$map": {
"input": {
"$objectToArray": "$array"
},
"in": {
"$cond": [
{
"$eq": [
{
"$type": "$$this.v"
},
"object"
]
},
{
"$objectToArray": "$$this.v"
},
[
"$$this"
]
]
}
}
},
"initialValue": [
],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
},
"in": "$$this.k"
}
}
]
}
}
}
]
With a little effort, we can get the key for all subdocument in an array field where the elements are object as well.
This works fine for me:
var arrayOfFieldNames = [];
var items = db.NAMECOLLECTION.find();
while(items.hasNext()) {
var item = items.next();
for(var index in item) {
arrayOfFieldNames[index] = index;
}
}
for (var index in arrayOfFieldNames) {
print(index);
}
Maybe slightly off-topic, but you can recursively pretty-print all keys/fields of an object:
function _printFields(item, level) {
if ((typeof item) != "object") {
return
}
for (var index in item) {
print(" ".repeat(level * 4) + index)
if ((typeof item[index]) == "object") {
_printFields(item[index], level + 1)
}
}
}
function printFields(item) {
_printFields(item, 0)
}
Useful when all objects in a collection has the same structure.
To get a list of all the keys minus _id, consider running the following aggregate pipeline:
var keys = db.collection.aggregate([
{ "$project": {
"hashmaps": { "$objectToArray": "$$ROOT" }
} },
{ "$group": {
"_id": null,
"fields": { "$addToSet": "$hashmaps.k" }
} },
{ "$project": {
"keys": {
"$setDifference": [
{
"$reduce": {
"input": "$fields",
"initialValue": [],
"in": { "$setUnion" : ["$$value", "$$this"] }
}
},
["_id"]
]
}
}
}
]).toArray()[0]["keys"];
I know I am late to the party, but if you want a quick solution in python finding all keys (even the nested ones) you could do with a recursive function:
def get_keys(dl, keys=None):
keys = keys or []
if isinstance(dl, dict):
keys += dl.keys()
list(map(lambda x: get_keys(x, keys), dl.values()))
elif isinstance(dl, list):
list(map(lambda x: get_keys(x, keys), dl))
return list(set(keys))
and use it like:
dl = db.things.find_one({})
get_keys(dl)
if your documents do not have identical keys you can do:
dl = db.things.find({})
list(set(list(map(get_keys, dl))[0]))
but this solution can for sure be optimized.
Generally this solution is basically solving finding keys in nested dicts, so this is not mongodb specific.
Based on #Wolkenarchitekt answer: https://stackoverflow.com/a/48117846/8808983, I write a script that can find patterns in all keys in the db and I think it can help others reading this thread:
"""
Python 3
This script get list of patterns and print the collections that contains fields with this patterns.
"""
import argparse
import pymongo
from bson import Code
# initialize mongo connection:
def get_db():
client = pymongo.MongoClient("172.17.0.2")
db = client["Data"]
return db
def get_commandline_options():
description = "To run use: python db_fields_pattern_finder.py -p <list_of_patterns>"
parser = argparse.ArgumentParser(description=description)
parser.add_argument('-p', '--patterns', nargs="+", help='List of patterns to look for in the db.', required=True)
return parser.parse_args()
def report_matching_fields(relevant_fields_by_collection):
print("Matches:")
for collection_name in relevant_fields_by_collection:
if relevant_fields_by_collection[collection_name]:
print(f"{collection_name}: {relevant_fields_by_collection[collection_name]}")
# pprint(relevant_fields_by_collection)
def get_collections_names(db):
"""
:param pymongo.database.Database db:
:return list: collections names
"""
return db.list_collection_names()
def get_keys(db, collection):
"""
See: https://stackoverflow.com/a/48117846/8808983
:param db:
:param collection:
:return:
"""
map = Code("function() { for (var key in this) { emit(key, null); } }")
reduce = Code("function(key, stuff) { return null; }")
result = db[collection].map_reduce(map, reduce, "myresults")
return result.distinct('_id')
def get_fields(db, collection_names):
fields_by_collections = {}
for collection_name in collection_names:
fields_by_collections[collection_name] = get_keys(db, collection_name)
return fields_by_collections
def get_matches_fields(fields_by_collections, patterns):
relevant_fields_by_collection = {}
for collection_name in fields_by_collections:
relevant_fields = [field for field in fields_by_collections[collection_name] if
[pattern for pattern in patterns if
pattern in field]]
relevant_fields_by_collection[collection_name] = relevant_fields
return relevant_fields_by_collection
def main(patterns):
"""
:param list patterns: List of strings to look for in the db.
"""
db = get_db()
collection_names = get_collections_names(db)
fields_by_collections = get_fields(db, collection_names)
relevant_fields_by_collection = get_matches_fields(fields_by_collections, patterns)
report_matching_fields(relevant_fields_by_collection)
if __name__ == '__main__':
args = get_commandline_options()
main(args.patterns)
As per the mongoldb documentation, a combination of distinct
Finds the distinct values for a specified field across a single collection or view and returns the results in an array.
and indexes collection operations are what would return all possible values for a given key, or index:
Returns an array that holds a list of documents that identify and describe the existing indexes on the collection
So in a given method one could do use a method like the following one, in order to query a collection for all it's registered indexes, and return, say an object with the indexes for keys (this example uses async/await for NodeJS, but obviously you could use any other asynchronous approach):
async function GetFor(collection, index) {
let currentIndexes;
let indexNames = [];
let final = {};
let vals = [];
try {
currentIndexes = await collection.indexes();
await ParseIndexes();
//Check if a specific index was queried, otherwise, iterate for all existing indexes
if (index && typeof index === "string") return await ParseFor(index, indexNames);
await ParseDoc(indexNames);
await Promise.all(vals);
return final;
} catch (e) {
throw e;
}
function ParseIndexes() {
return new Promise(function (result) {
let err;
for (let ind in currentIndexes) {
let index = currentIndexes[ind];
if (!index) {
err = "No Key For Index "+index; break;
}
let Name = Object.keys(index.key);
if (Name.length === 0) {
err = "No Name For Index"; break;
}
indexNames.push(Name[0]);
}
return result(err ? Promise.reject(err) : Promise.resolve());
})
}
async function ParseFor(index, inDoc) {
if (inDoc.indexOf(index) === -1) throw "No Such Index In Collection";
try {
await DistinctFor(index);
return final;
} catch (e) {
throw e
}
}
function ParseDoc(doc) {
return new Promise(function (result) {
let err;
for (let index in doc) {
let key = doc[index];
if (!key) {
err = "No Key For Index "+index; break;
}
vals.push(new Promise(function (pushed) {
DistinctFor(key)
.then(pushed)
.catch(function (err) {
return pushed(Promise.resolve());
})
}))
}
return result(err ? Promise.reject(err) : Promise.resolve());
})
}
async function DistinctFor(key) {
if (!key) throw "Key Is Undefined";
try {
final[key] = await collection.distinct(key);
} catch (e) {
final[key] = 'failed';
throw e;
}
}
}
So querying a collection with the basic _id index, would return the following (test collection only has one document at the time of the test):
Mongo.MongoClient.connect(url, function (err, client) {
assert.equal(null, err);
let collection = client.db('my db').collection('the targeted collection');
GetFor(collection, '_id')
.then(function () {
//returns
// { _id: [ 5ae901e77e322342de1fb701 ] }
})
.catch(function (err) {
//manage your error..
})
});
Mind you, this uses methods native to the NodeJS Driver. As some other answers have suggested, there are other approaches, such as the aggregate framework. I personally find this approach more flexible, as you can easily create and fine-tune how to return the results. Obviously, this only addresses top-level attributes, not nested ones.
Also, to guarantee that all documents are represented should there be secondary indexes (other than the main _id one), those indexes should be set as required.
We can achieve this by Using mongo js file. Add below code in your getCollectionName.js file and run js file in the console of Linux as given below :
mongo --host 192.168.1.135 getCollectionName.js
db_set = connect("192.168.1.135:27017/database_set_name"); // for Local testing
// db_set.auth("username_of_db", "password_of_db"); // if required
db_set.getMongo().setSlaveOk();
var collectionArray = db_set.getCollectionNames();
collectionArray.forEach(function(collectionName){
if ( collectionName == 'system.indexes' || collectionName == 'system.profile' || collectionName == 'system.users' ) {
return;
}
print("\nCollection Name = "+collectionName);
print("All Fields :\n");
var arrayOfFieldNames = [];
var items = db_set[collectionName].find();
// var items = db_set[collectionName].find().sort({'_id':-1}).limit(100); // if you want fast & scan only last 100 records of each collection
while(items.hasNext()) {
var item = items.next();
for(var index in item) {
arrayOfFieldNames[index] = index;
}
}
for (var index in arrayOfFieldNames) {
print(index);
}
});
quit();
Thanks #ackuser
Following the thread from #James Cropcho's answer, I landed on the following which I found to be super easy to use. It is a binary tool, which is exactly what I was looking for:
mongoeye.
Using this tool it took about 2 minutes to get my schema exported from command line.
I know this question is 10 years old but there is no C# solution and this took me hours to figure out. I'm using the .NET driver and System.Linq to return a list of the keys.
var map = new BsonJavaScript("function() { for (var key in this) { emit(key, null); } }");
var reduce = new BsonJavaScript("function(key, stuff) { return null; }");
var options = new MapReduceOptions<BsonDocument, BsonDocument>();
var result = await collection.MapReduceAsync(map, reduce, options);
var list = result.ToEnumerable().Select(item => item["_id"].ToString());
This one lines extracts all keys from a collection into a comma separated sorted string:
db.<collection>.find().map((x) => Object.keys(x)).reduce((a, e) => {for (el of e) { if(!a.includes(el)) { a.push(el) } }; return a}, []).sort((a, b) => a.toLowerCase() > b.toLowerCase()).join(", ")
The result of this query typically looks like this:
_class, _id, address, city, companyName, country, emailId, firstName, isAssigned, isLoggedIn, lastLoggedIn, lastName, location, mobile, printName, roleName, route, state, status, token
I extended Carlos LM's solution a bit so it's more detailed.
Example of a schema:
var schema = {
_id: 123,
id: 12,
t: 'title',
p: 4.5,
ls: [{
l: 'lemma',
p: {
pp: 8.9
}
},
{
l: 'lemma2',
p: {
pp: 8.3
}
}
]
};
Type into the console:
var schemafy = function(schema, i, limit) {
var i = (typeof i !== 'undefined') ? i : 1;
var limit = (typeof limit !== 'undefined') ? limit : false;
var type = '';
var array = false;
for (key in schema) {
type = typeof schema[key];
array = (schema[key] instanceof Array) ? true : false;
if (type === 'object') {
print(Array(i).join(' ') + key+' <'+((array) ? 'array' : type)+'>:');
schemafy(schema[key], i+1, array);
} else {
print(Array(i).join(' ') + key+' <'+type+'>');
}
if (limit) {
break;
}
}
}
Run:
schemafy(db.collection.findOne());
Output
_id <number>
id <number>
t <string>
p <number>
ls <object>:
0 <object>:
l <string>
p <object>:
pp <number>
I was trying to write in nodejs and finally came up with this:
db.collection('collectionName').mapReduce(
function() {
for (var key in this) {
emit(key, null);
}
},
function(key, stuff) {
return null;
}, {
"out": "allFieldNames"
},
function(err, results) {
var fields = db.collection('allFieldNames').distinct('_id');
fields
.then(function(data) {
var finalData = {
"status": "success",
"fields": data
};
res.send(finalData);
delteCollection(db, 'allFieldNames');
})
.catch(function(err) {
res.send(err);
delteCollection(db, 'allFieldNames');
});
});
After reading the newly created collection "allFieldNames", delete it.
db.collection("allFieldNames").remove({}, function (err,result) {
db.close();
return;
});
I have 1 simpler work around...
What you can do is while inserting data/document into your main collection "things" you must insert the attributes in 1 separate collection lets say "things_attributes".
so every time you insert in "things", you do get from "things_attributes" compare values of that document with your new document keys if any new key present append it in that document and again re-insert it.
So things_attributes will have only 1 document of unique keys which you can easily get when ever you require by using findOne()

JS build object recursively

I am attempting to build a file-structure index using nodeJS. I'm using the fs.readir function to iterate the files, which works fine. My problem is descending into the directory structure and returning a full object with the correct structure.
I have a simple function named identify which, when given file name "myfile.txt" will return an object {name: "myfile", type: "txt"}, which will explain that part of the function below...
My problem is that nothing is being returned when I run the indexer into the "me" variable. The console.log(results) line does return, however. This leaves me quite confused.
Any help would be greatly appreciated!
indexer =
function(directory){
Self.indexleft++;
var results = {};
Self.client.readdir(directory, function(err,fileLst){
if(err){ return; }
for(var count=0; count < fileLst.length; count++){
var ident = identify(fileLst[count]);
if(ident.type = 'dir'){
var descendant = (directory !== '') ?
directory + '\\' + ident.name : ident.name;
ident.children = indexer(descendant);
}
//directory = (directory.split('\\').pop());
results[ident.name] = ident;
}
console.log(results);
return results;
});
}
var me = indexer(''); console.log(me);
EDIT::
I've actually got something working now, though it's not quite as elegant as I'd like. Below is what I did. If anyone has a suggestion on optimizing I'd be happy to hear it!!
Newest (working) Code:
var events = require('events'),
event = new events.EventEmitter(),
setToValue = function(obj, value, path) {
path = path.split('\\');
for (i = 0; i < path.length - 1; i++)
obj = obj[path[i]];
obj[path[i]] = value;
},
identify = function(file){
var split = file.split('.'),
type = (split.length > 1) ? split.pop() : 'dir',
filename = split.join('.');
return { name: filename, type: type };
};
Indexer = function(cli,dir,callback){
this.client = cli; // File reading client
this.startDir = dir; // Starting directory
this.results = {}; // Result object
this.running = 0; // How many itterations of start() are running
this.start(dir); // Start indexing
this.monit(); // Start never returns anything, monit() checks ever 5 seconds and will fire callback if 0 itterations are running.
this.callbackDone = false; // Checks whether the callback has already been fired. Important in case of interval staggering
this.cb = callback;
}
Indexer.prototype = {
start: function(directory){
var Self = this;
Self.running++;
Self.client.readdir(directory, function(err,fileLst){
if(err){ Self.running--; return; }
for(var count=0; count < fileLst.length; count++){
var ident = identify(fileLst[count]);
var descendant = (directory !== '') ? directory + '\\' + ident.name : ident.name;
if(ident.type === 'dir'){
Self.start(descendant);
}
setToValue(Self.results, ident, descendant);
}
Self.running--;
console.log('running' + Self.running);
});
},
monit: function(){
var Self = this;
Self.intervalA = setInterval(function(){
if(Self.running < 1){
if(!Self.callbackDone){
this.callbackDone=true;
Self.cb(Self.results);
}
clearInterval(Self.intervalA);
}
}, 5000)
}
}
var ix = new Indexer(Self.client,'',function(res){
console.log("Index Complete!");
fs.writeFile(path.join(Self.localLibBase,'/index.json'), JSON.stringify(res), (err)=> {
console.log("FileWrite Complete!");
});
});
Example of returned object structure :
{
"Applications" : {
"name" : "Applications",
"type" : "dir",
"Microsoft Exchange Server 2007" : {
"name" : "Microsoft Exchange Server 2007",
"type" : "dir",
"Microsoft Exchange Server 2007 SP1" : {
"name" : "Microsoft Exchange Server 2007 SP1",
"type" : "iso"
}
}
}
}
The result is only available asynchronously, so you are trying to output the result too soon. The inner code is only executed later.
You can solve this in many ways. A very nice solution to working with asynchronous code is using promises.
As you have a recursive call, you'll have to resolve that with promises too.
NB: Note you had a bug in the comparison with "dir": you assigned instead of comparing.
Here is how your code would look:
var indexer = function(directory) {
// return a promise object
return new Promise(function (resolve, reject) {
Self.indexleft++;
var results = {};
Self.client.readdir(directory, function(err,fileLst){
if(err) {
reject(); // promise is rejected
return;
}
// "Iterate" over file list asyonchronously
(function nextFile(fileList) {
if (!fileList.length) {
resolve(results); // promise is resolved
return;
}
var file = fileLst.shift(); // shop off first file
var ident = identify(file);
results[ident.name] = ident;
if(ident.type === 'dir'){ // There was a bug here: equal sign!
var descendant = directory !== ''
? directory + '\\' + ident.name : ident.name;
// recursively call indexer: it is again a promise!
indexer(descendant).then(function (result) {
ident.children = result;
// recursively continue with next file from list
nextFile(fileList);
});
} else {
nextFile(fileLst);
}
})(fileLst); // start first iteration with full list
});
});
};
// Call as a promise. Result is passed async to callback.
indexer('').then(function(me) {
console.log(me);
});
I made some dummy functions for your external references to make this snippet work:
// Below code added to mimic the external references -- can be ignored
var filesystem = [
"",
"images",
"images\\photo.png",
"images\\backup",
"images\\backup\\old_photo.png",
"images\\backup\\removed_pic.jpg",
"images\\panorama.jpg",
"docs",
"docs\\essay.doc",
"readme.txt",
];
var Self = {
indexLeft: 0,
client: {
readdir: function (directory, callback) {
var list = filesystem.filter( path =>
path.indexOf(directory) == 0
&& path.split('\\').length == directory.split('\\').length + (directory!=='')
&& path !== directory
).map ( path => path.split('\\').pop() );
setTimeout(callback.bind(null, 0, list), 100);
}
}
}
function identify(item) {
return {
name: item,
type: item.indexOf('.') > -1 ? 'file' : 'dir'
};
}
// Above code added to mimic the external references -- can be ignored
var indexer = function(directory) {
// return a promise object
return new Promise(function (resolve, reject) {
Self.indexleft++;
var results = {};
Self.client.readdir(directory, function(err,fileLst){
if(err) {
reject(); // promise is rejected
return;
}
// "Iterate" over file list asyonchronously
(function nextFile(fileList) {
if (!fileList.length) {
resolve(results); // promise is resolved
return;
}
var file = fileLst.shift(); // shop off first file
var ident = identify(file);
results[ident.name] = ident;
if(ident.type === 'dir'){ // There was a bug here: equal sign!
var descendant = directory !== ''
? directory + '\\' + ident.name : ident.name;
// recursively call indexer: it is again a promise!
indexer(descendant).then(function (result) {
ident.children = result;
// recursively continue with next file from list
nextFile(fileList);
});
} else {
nextFile(fileLst);
}
})(fileLst); // start first iteration with full list
});
});
};
// Call as a promise. Result is passed async to callback.
indexer('').then(function(me) {
console.log(me);
});
It's not really obvious how you're expecting to that returned object from the code you have, but I can help you get the object nonetheless.
The shape of the object is bad because you're using filenames as keys on the object but that's wrong. Keys should be identifiers known to your program, and since filenames can be almost anything, using a filename as a key is terrible.
For example, consider if a file was named name in your structure
{ "Applications" : {
"name" : "Applications",
"type" : "dir",
"name" : {
"name" : "name"
... } } }
Yep, it just broke. Don't worry tho, our solution won't run into such troubles.
const co = require('co')
const {stat,readdir} = require('fs')
const {extname,join} = require('path')
// "promisified" fs functions
const readdirp = path =>
new Promise ((t,f) => readdir (path, (err, res) => err ? f (err) : t (res)))
const statp = fd =>
new Promise ((t,f) => stat (fd, (err,stats) => err ? f (err) : t (stats)))
// tree data constructors
const Dir = (path, children) =>
({type: 'd', path, children})
const File = (path, ext) =>
({type: 'f', path, ext})
// your function
const indexer = function* (path) {
const stats = yield statp (path)
if (stats.isDirectory ())
return Dir (path, yield (yield readdirp (path)) .map (p => indexer (join (path,p))))
else
return File (path, extname (path))
}
This is good design because we didn't tangle directory tree building in with whatever Self.client is. Parsing a directory and building a tree is its own thing, and if you need an Object to inherit that behaviour there are other ways to do it.
Ok let's setup a sample tree of files and then run it
$ mkdir test
$ cd test
$ mkdir foo
$ touch foo/disk.iso foo/image.jpg foo/readme.txt
$ mkdir foo/bar
$ touch foo/bar/build foo/bar/code.js foo/bar/migrate.sql
Using indexer is easy
// co returns a Promise
// once indexer is done, you will have a fully built tree
co (indexer ('./test')) .then (
tree => console.log (JSON.stringify (tree, null, ' ')),
err => console.error (err.message)
)
Output (some \n removed for brevity)
{
"type": "d",
"path": "./foo",
"children": [
{
"type": "d",
"path": "foo/bar",
"children": [
{ "type": "f", "path": "foo/bar/build", "ext": "" },
{ "type": "f", "path": "foo/bar/code.js", "ext": ".js" },
{ "type": "f", "path": "foo/bar/migrate.sql", "ext": ".sql" }
]
},
{ "type": "f", "path": "foo/disk.iso", "ext": ".iso" },
{ "type": "f", "path": "foo/image.jpg", "ext": ".jpg" },
{ "type": "f", "path": "foo/readme.txt", "ext": ".txt" }
]
}
If you try indexer on a path to a file, it will not fail
co (indexer ('./test/foo/disk.iso')) .then (
tree => console.log (JSON.stringify (tree, null, ' ')),
err => console.error (err.message)
)
Output
{ "type": "f", "path": "./foo/disk.iso", "ext": ".iso" }

How could I pass 'context' with a stream?

I have a simple app running that pipes objects through a stream, like so:
new ReadStreamThatCreatesData()
.pipe(new TransformerStream())
.pipe(new WriteStreamThatActsOnData()
But I want the WriteStreamThatActsOnData to have access to a property in from the ReadStreamThatCreatesData, without the TransformerStream having to know about it or be able to access it. The pseudocode of what I'd want is basically this:
new ReadStreamThatCreatesData()
.storeContext((obj) => obj.property)
.pipe(new TransformerStream())
.retrieveContext((obj, context) => obj.property = context)
.pipe(new WriteStreamThatActsOnData()
but given the nature of streams I don't really see how that's possible. Does anyone have any smart ideas on how I could do something like this?
One way I can think to do this is to pipe the ReadStreamThatCreatesData to a function that splits it into two different streams: one stream of the context properties you have pulled out of the item and another stream with the remainder of the object. You pipe the second stream into the TransformerStream and then pipe that output along with the context stream through a zip operator that combines the two streams back into one. Then you send that to the WriteStreamThatActsOnData.
I don't think there are built-in node.js functions to do this, but you can use another library such as RxJS or highland.
Here's an example implementation with highland:
'use strict';
/*
Put this content into input.txt. Make sure there are no blank lines in the file:
{ "secret": 1, "val": "a" }
{ "secret": 2, "val": "b" }
{ "secret": 3, "val": "c" }
{ "secret": 4, "val": "d" }
{ "secret": 5, "val": "e" }
After running, output.txt should have this content:
{"val":"A","secret":1}
{"val":"B","secret":2}
{"val":"C","secret":3}
{"val":"D","secret":4}
{"val":"E","secret":5}
*/
const fs = require('fs');
const stream = require('stream');
const highland = require('highland');
const input = fs.createReadStream('input.txt');
const output = fs.createWriteStream('output.txt');
function readStreamThatCreatesData() {
return highland(input).split().map(JSON.parse);
}
class TransformerStream extends stream.Transform {
constructor(options) {
if (!options) {
options = {};
}
options.objectMode = true;
super(options);
}
_transform(item, enc, cb) {
if (item.secret) {
item.secret = 'removed';
}
item.val = item.val.toUpperCase();
this.push(item);
cb();
}
};
function removeSecret(item) {
delete item.secret;
return item;
}
function extractSecret(item) {
return item.secret;
}
const inputStream = readStreamThatCreatesData();
const secretStream = inputStream.fork().map(extractSecret);
const mainStream = inputStream.fork().map(removeSecret);
secretStream.zip(highland(mainStream.pipe(new TransformerStream())))
.map((combined) => {
const secret = combined[0];
const item = combined[1];
item.secret = secret;
return JSON.stringify(item) + '\n';
})
.pipe(output);

Categories

Resources