How to define a Date Scalar using GraphQL-JS? - javascript

I am trying to define a custom scalar in GraphQL so I can query & process the Dates in my MongoDB collections. I am not sure I understand 100% what a scalar is or does, but it seems to be a sort of type that I define myself. All the examples & tutorials I found were using Apollo or some other type of notation, but I would like to see a solution using GraphQL-JS
So far, I have defined my scalar like so:
const Date = new GraphQLScalarType({
name: "Date",
serialize: (value) => {
return value; //is it correct, to just return the value? Do I need to parse it or turn it into a Date first?
},
parseValue: () => {
return "serialise"; //I am just returning this string here, because I do not know what this function is for
},
parseLiteral(ast) {
return null; //I am just returning null here, because I do not know what this function is for
},
});
I am not sure I understand what each of these functions are supposed to do. And wouldn't there also have to be a deserialize function?
When I query now against my graphql endpoint I do get back something like:
{
"myDate": "2020-07-15T00:00:00.000Z"
}
I guess that my serialise function is at play here? The Date is certainly correct, but I am not sure if I should do anything else with the data before returning it from serialize? Right now I just return whatever I get from my MongoDB database.

Urigo, from The Guild, created graphql-scalars that contains definitions for multiple common scalars used in GraphQL
//is it correct, to just return the value? Do I need to parse it or turn it into a Date first?
It would be wise to validate that value is a Date before returning it.
And yes, just return the value.
//I am just returning null here, because I do not know what this function is for
This is the entry from the abstract-syntax-tree (ast).
See Urigo's code below to see how the ast object is accessed
ast.kind
ast.value
Additionally, take a look at this SO post that describes the difference between parseValue and parseLiteral
Take a look at localDate and that may provide you the example you need to answer your question :)
https://github.com/Urigo/graphql-scalars/blob/master/src/scalars/LocalDate.ts#L34
export const GraphQLLocalDate = /*#__PURE__*/ new GraphQLScalarType({
name: 'LocalDate',
description:
'A local date string (i.e., with no associated timezone) in `YYYY-MM-DD` format, e.g. `2020-01-01`.',
serialize(value) {
// value sent to client as string
return validateLocalDate(value);
},
parseValue(value) {
// value from client as json
return validateLocalDate(value);
},
parseLiteral(ast) {
// value from client in ast
if (ast.kind !== Kind.STRING) {
throw new GraphQLError(
`Can only validate strings as local dates but got a: ${ast.kind}`,
);
}
return validateLocalDate(ast.value);
},
});

Related

Is there a better way that to Cast mongoose.Types.ObjectID references to String in order to compare them with == in some cases? [duplicate]

I have a node.js application that pulls some data and sticks it into an object, like this:
var results = new Object();
User.findOne(query, function(err, u) {
results.userId = u._id;
}
When I do an if/then based on that stored ID, the comparison is never true:
if (results.userId == AnotherMongoDocument._id) {
console.log('This is never true');
}
When I do a console.log of the two id's, they match exactly:
User id: 4fc67871349bb7bf6a000002 AnotherMongoDocument id: 4fc67871349bb7bf6a000002
I am assuming this is some kind of datatype problem, but I'm not sure how to convert results.userId to a datatype that will result in the above comparison being true and my outsourced brain (aka Google) has been unable to help.
Mongoose uses the mongodb-native driver, which uses the custom ObjectID type. You can compare ObjectIDs with the .equals() method. With your example, results.userId.equals(AnotherMongoDocument._id). The ObjectID type also has a toString() method, if you wish to store a stringified version of the ObjectID in JSON format, or a cookie.
If you use ObjectID = require("mongodb").ObjectID (requires the mongodb-native library) you can check if results.userId is a valid identifier with results.userId instanceof ObjectID.
Etc.
ObjectIDs are objects so if you just compare them with == you're comparing their references. If you want to compare their values you need to use the ObjectID.equals method:
if (results.userId.equals(AnotherMongoDocument._id)) {
...
}
converting object id to string(using toString() method) will do the job.
The three possible solutions suggested here have different use cases.
Use .equals when comparing ObjectId on two mongoDocuments like this
results.userId.equals(AnotherMongoDocument._id)
Use .toString() when comparing a string representation of ObjectId to an ObjectId of a mongoDocument. like this
results.userId === AnotherMongoDocument._id.toString()
According to the above,i found three ways to solve the problem.
AnotherMongoDocument._id.toString()
JSON.stringify(AnotherMongoDocument._id)
results.userId.equals(AnotherMongoDocument._id)
The accepted answers really limit what you can do with your code. For example, you would not be able to search an array of Object Ids by using the equals method. Instead, it would make more sense to always cast to string and compare the keys.
Here's an example answer in case if you need to use indexOf() to check within an array of references for a specific id. assume query is a query you are executing, assume someModel is a mongo model for the id you are looking for, and finally assume results.idList is the field you are looking for your object id in.
query.exec(function(err,results){
var array = results.idList.map(function(v){ return v.toString(); });
var exists = array.indexOf(someModel._id.toString()) >= 0;
console.log(exists);
});
I faced exactly the same problem and i simply resolved the issue with the help of JSON.stringify() as follow:-
if (JSON.stringify(results.userId) === JSON.stringify(AnotherMongoDocument._id)) {
console.log('This is never true');
}
Mongoose from 5 to 6 migration guide:
"Mongoose now adds a valueOf() function to ObjectIds. This means you can now use == to compare an ObjectId against a string."
https://mongoosejs.com/docs/migrating_to_6.html#objectid-valueof
Here is an example that explains the issue and why it confusing for many. Only the first console log shows the object in its true form, and any other debuging/loging will be confusing because they look the same.
// Constructor for an object that has 'val' and some other stuffs
// related to to librery...
function ID(_val) {
this.val = _val;
this.otherStuff = "other stuffs goes here";
}
// function to help user get usefull infos from the Object
ID.prototype.toString = function toString() {
return `${this.val}`;
};
// Create new Object of type ID
const id = new ID('1234567');
console.log("my ID: ", id); // my ID: Object {
// val: "1234567",
// otherStuff: "other stuffs goes here"
// }
console.log("my ID: " + id); // my ID: 1234567
console.log(id === '1234567'); // false
console.log(id == '1234567'); // true
console.log(id.toString() === '1234567'); //true
console.log(`${id}` === '1234567'); // true
console.log(new ID('1234567') === id); // false

Converting large number to string in Javascript/Node

I have seen other related questions, but they did not solve my problem, or may be I somehow missed the exactly same resolved queries.
Here is the problem. The service that I call up returns a JSON response with some keys having large numbers as values, and I want to pass them on to my view and display. The issue is that they are getting rounded off, which I don't want. Actually its coming inside a buffer from which I am doing now:
JSON.parse(res.body.toString()) // res.body is a Buffer
and sending to view. How can I retain the whole number in the form of a string and send this to view so exactly the same is made available to UI.
I thought may be a replacer will help, but it does not works too.
const replacer = (key, value) => {
if (typeof value === 'number') {
return JSON.stringify(value);
}
return value;
};
//78787878977987787897897897123456786747398
const obj = {
name: 'John',
income: 78787878977987787897897897123456786747398,
car: null
};
var buf = Buffer.from(JSON.stringify(obj));
console.log(buf.toString());
// console.log(JSON.stringify(buf.toString()))
// console.log('func res: ', replacer('key', 78787878977987787897897897123456786747398))
// console.log(obj.income.toString())
console.log(JSON.stringify(obj, replacer));
You can recommend some external trusted library, or better, suggest me the solution through direct code only.
Edit:
The outcome in short is: Convert the response to String before returning from the server. Once it gets into JS (Buffer in my case), the conversion already occurred meaning that from the application side, nothing can be done to retrieve it.
Please let me know if there's a real solution to this without modifying server response.
Unfortunately, the number is higher than max_safe_integer, so if it ever gets parsed as a number, even if it's converted back to a string later (such as with the reviver function, the second parameter to JSON.parse), it won't be reliable. But luckily, since you have a JSON string, you can replace numeric values with string values before JSON.parseing it. For example:
const resBody = '{"foo":"bar", "objs":[{"name":"John", "income": 78787878977987787897897897123456786747398}]}';
const resBodyReplaced = resBody.replace(/: *(\d+)/g, ':"$1"');
console.log(JSON.parse(resBodyReplaced).objs[0].income);

How do we make JavaScript think that a string is JSON?

I have a function which only works if JSON object is passed to it. If I pass a string to it, with same format as JSON, it doesnt work. So I want to make that function think that the string passed to it is a JSON. The string is indeed in the JSON format.
I also tried the following. I inputted the string through Ajax , with "handle as" parameter as "JSON", and then when I passed the result to the function it works.
So I deduced the problem is not with the string. How do i convert this string to JSON? If i get same string through ajax request and then passing it to function works, whereas directly passing it doesnt work.
Screenshot of console log:
Advance Thanks
JavaScript is a dynamically typed language, so you can't really specify types of function arguments up front. If you want that, look into TypeScript.
JSON is nothing but a String, not a standalone type in JavaScript. To convert a JavaScript object to the equivalent JSON string representation use:
const myJSON = JSON.stringify ({ a: 'b' })
To get a JavaScript object back from a JSON string use:
JSON.parse (myJSON)
When going over the network, libraries can automatically return serialized objects if you specify the type as JSON. You haven't pasted any code, so I'm guessing that you haven't specified application/json as the Content-Type in your network request. Since you've tagged your question jQuery, take a look at [$.getJSON][2], this should do what you want.
Use JSON.parse
function isJson(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
Example:
function isJson(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
str = '{"a":1, "b":2}';
console.log(isJson(str)); // true
str1 = '{["a":1, "b":2]}';
console.log(isJson(str1)); // false

Dates in a Typescript interface are actually strings when inspected

Unfortunately the total code to reproduce this would be extensive, so I'm hoping that my problem is obvious with what I can easily provide. If required, I'll post a more complete solution.
First, I am defining an interface:
export interface ITest {
myDate: Date;
}
Then I create an array of these for testing:
export const TEST: ITest[]=[{myDate: new Date(1995, 8, 1)}]
I expose these using a service in Angular2 that is hitting the InMemoryDbService from angular2-in-memory-web-api. The code where I call this and get the array is as follows:
get(): Promise<ITest[]>{
return this.http.get(this.testUrl)
.toPromise()
.then(response => response.json().data as ITest[])
.catch(this.handleError);
}
...and then I bring it into my component with:
this.testService.get().then(tests => {
this.tests = tests;
console.log("Date Type:"+typeof(this.tests[0].myDate));
});
This all works fine, but the problem is the console.log statement shown there results in:
Date Type:string
The data in it is correct, in that the string held by my date is 1995-09-01T07:00:00.000Z, but the main issue is - it isn't a Date it is a string! In VS Code I even get code completion for methods like toDateString but when I execute them I (of course) get toDateString is not a function.
I am pretty sure that the issue is occurring with response => response.json().data as ITest[], but why isn't the date data being cast to an actual Date? I'm doing it wrong, that I understand. How should I be handling this such that I can have my objects obtain the types I expect?
You are using an interface and type assertion to basically tell TypeScript that the object conforms to that interface.
But this is not actually the case as what you are casting is a json object in which the "myDate" property is being represented as a string.
Using type-assertion does not affect generated javascript code in any way - you have to do the actual type conversion yourself.
The reason why it comes as string is that there is no type defined in JSON format to represent Date, so the server in all likelihood is just sending a result of date.toString().
One option for you would be to have a class for representing the returned value and instantiate an object from the JSON properties like so:
var response = JSON.parse(JSON.stringify({ myDate: new Date() }));
class Test {
constructor(json: { myDate: string }) {
this.myDate = new Date(json.myDate);
}
myDate: Date;
}
let test = new Test(response);
console.log("Type: " + typeof (test.myDate));
console.log("Value: " + test.myDate);

Conditional upsert based on custom id of nested documents in Meteor

This doesn't work, but hopefully this and the mouthful of a title gets the point across
function addToDB(account_id)
{
obj = {};
if (!Accounts.findOne({account_id: account_id})) obj.createdAt = new Date().valueOf();
obj.account_id = account_id;
Accounts.upsert({account_id: account_id}, {$set: obj});
}
I need to use the account_id instead of the MongoDB object id, so it has to be indexable/unique. If it's an update, the createdAt shouldn't change.
UPDATED so it works in the original context, but I prefer the solution I comment with to the correct answer.
I doubt this is supported in the minimongo side of things, but you can always implement the logic on the server and publish the method. But MongoDB supplies a $setOnInsert operator which has the purpose of only applying that value to the update performed when the "upsert" in fact inserts a new document:
Accounts.upsert(
{ "account_id": obj.account_id },
{
"$setOnInsert": { "created_at": new Date() },
"$set": obj // account_id would be automatically added but it doesn't matter
}
);
Of course presuming that obj does not already contain a a created_at field and the data you want is actually a BSON "Date" type rather than an epoch timestamp ( BSON Dates actually store as an epoch timestamp internally anyway ) otherwise convert using .valueOf() as shown in your example.

Categories

Resources