JSON.stringify extra quotes - javascript

Consider:
var d = new Date();
var j = d.toJSON();
var s = JSON.stringify(d);
console.log for each of the variables returns:
Tue Jul 29 2014 13:27:19 GMT+0200 (W. Europe Summer Time)
2014-07-29T11:27:19.253Z // a string
"2014-07-29T11:27:19.253Z" // same string as above but packed in ""
I expected them to return the same thing, but then I read
http://www.json.org/js.html:
If the stringify method sees an object that contains a toJSON method,
it calls that method, and stringifies the value returned. This allows
an object to determine its own JSON representation.
and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify:
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON method customizes JSON stringification
behavior: instead of the object being serialized, the value returned
by the toJSON method when called will be serialized. For example:
Does this mean that I always have to do something like:
var d = new Date();
var j = d.toJSON();
var s;
if (d.toJSON) {
s = d.toJSON();
} else {
s = JSON.stringify(d);
}
to ensure that s == j, since I can't rely on JSON.stringify not performing two serialisations?
EDIT
In light of jgillich's answer, the following code helps clarify things (for me at least):
var s = "xxx"
s = JSON.stringify(s)
s = JSON.stringify(s)
s = JSON.stringify(s)
s = JSON.stringify(s)
s = JSON.stringify(s)
console.log(s)
returns:
""\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"xxx\\\\\\\\\\\\\\\"\\\\\\\"\\\"\"""
i.e. JSON.stringify returns not a string representation but rather a serialisation of an object. You'd think I'd realise that from the name and the presence of toString.

toJSON() is not a function that is meant to return JSON. Instead, it is a function that, when it exists, is called before the JSON serialization happens. The following are exactly the same:
JSON.stringify(new Date().toJSON()); // toJSON called manually
JSON.stringify(new Date()); // toJSON called by the serializer

Related

return standalone value from constructor not object

While learning the mongoDB I notice one constructor returning string with prototype function. For example
Following code will generate a random string, first 4 letter from string contain timestamp and to get the time stamp it has the prototype function.
const id = new ObjectID() // 5c8766f4bcd712911ab81eea
console.log(id.getTimestamp()) // will print the date
I was just working how it been done. I am trying to create the simple constructor in same way but it always return object. How can we make it to return single value instead of object with prototype function.
function getMyTime(){
this.timestamp = new Date().getTime()
}
const timestamp = new getMyTime(); // should print timestamp 1552385374255
console.log(timestamp.printReadableDate()) // should print date Tue Mar 12 2019 15:39:34 GMT+0530 (India Standard Time)
Edited
class GetTime {
constructor(){
this.time = new Date().getTime();
}
getActualDate(){
}
toString(){
return this.time
}
}
const stamp = new GetTime();
console.log(stamp) // should be 1552472230797
console.log(stamp.getActualDate()) //should be Wed Mar 13 2019 15:47:10 GMT+0530 (India Standard Time)
The ObjectId class used by MongoDB is ultimately the ObjectId class provided by js-bson. This ObjectId class uses a facility provided by Node.js util.inspect to get itself printed as a string:
ObjectId.prototype[util.inspect.custom || 'inspect'] = ObjectId.prototype.toString;
What is going on there is that js-bson adds a method which has for name either util.inspect.custom or "inspect". The 2nd name is used for compatibility with Node versions prior to Node 6.6.0. In node, console.log uses util.format, which ultimately uses util.inspect to determine how to print each object passed to console.log, and it is possible to customize what util.inspect does by adding a method on the object that we want to customize. For Node versions prior to 6.6.0 the method must be named "inspect", for later versions it must have for name the value of util.inspect.custom.
So you can take a page out of what js-bson does and implement your class with:
const util = require("util");
class GetTime {
constructor(){
this.time = new Date().getTime();
}
toString() {
return this.time;
}
}
GetTime.prototype[util.inspect.custom || 'inspect'] = GetTime.prototype.toString
const stamp = new GetTime();
console.log(stamp);
Or this syntax might be more palatable to you or the development tools you use. I have used some code documentation tools in the past that are definitely more at ease with the following:
const util = require("util");
class GetTime {
constructor(){
this.time = new Date().getTime();
}
toString() {
return this.time;
}
[util.inspect.custom || "inspect"]() {
return this.toString();
}
}
const stamp = new GetTime();
console.log(stamp);
This syntax provides the same result as the earlier one, though there is an additional call.
As I mentioned above, this trick works only in Node. If I try passing an ObjectId object to console.log in Chrome, then Chrome does what it does with every other object: print the constructor name and the fields of the object. As far as I can tell, there's no cross-platform way to customize how console.log prints objects.
A constructor returns an object by definition.
The MongoDB JavaScript driver's ObjectID returns a "new ObjectID instance", not a string. But the ObjectID class also implements a toString method which returns a string representation of the ObjectID object instance.
A constructor called with new always returns an object. Almost everything in javascript is an object, but some objects are treated differently, like Strings.
You can, however, "fool" the interpreter by extending one of the base object types:
class ObjectID extends String {
constructor () {
// Call String constructor with random hex
super(Math.floor(Math.random() * 16777215).toString(16))
this.timestamp = Date.now()
}
getTimestamp () {
return this.timestamp
}
}
const id = new ObjectId() // { [String: '2fff78'] timestamp: 1552718634749 }
console.log(`Id is ${id}`) // "Id is 2fff78"
console.log(id.getTimestamp()) // 1552718634749
Note: this is a very bad idea.

Why are Date treated like string with + and like number for any other arithmetic operators?

From my understanding if an object has valueOf function it will be used when object needs to be converted into a primitive. If an object has toString function it will be used when object needs to be converted into a string. So when you use + it should first use valueOf if present and then toString.
That's what it seems to do from :
var obj1 = {
valueOf: () => 0
};
var obj2 = {
toString: () => 'a'
};
var obj3 = {
valueOf: () => 0,
toString: () => 'a'
};
obj1 + obj1; // -> 0
obj2 + obj2; // -> 'aa'
obj3 + obj3; // -> 0
Now Date is implementing both valueOf and toString like obj3 but when you do date + date you get a string !
Using any other arithmetic operators will treat date as a number do the operation and return a valid number.
Here is some code to demonstrate my confusion :
var date = new Date(2017,1,1);
date - date; // -> 0
date * date: // -> 2.20790950849296e+24
date / date; // -> 1
date % date; // -> 0
date + date; // -> "Wed Feb 01 2017 00:00:00 GMT+0100 (CET)Wed Feb 01 2017 00:00:00 GMT+0100 (CET)"
That is because Date has an exotic ##toPrimitive which prefers string over number. See #sec-date.prototype-##toprimitive
Date objects, are unique among built-in ECMAScript object in that they
treat "default" as being equivalent to "string", All other built-in
ECMAScript objects treat "default" as being equivalent to "number".
The + operator will act as a concatenator in JS if the first operand is a string. Since date yields a string, it will act as a concatenator in this case.
An Object can be converted to a primitive value with the help of the Symbol.toPrimitive property (used as a function value).
The function is called with a string argument hint, which specifies the preferred type of the result primitive value. The hint argument can be one of "number", "string", and "default".
Syntax for Date objects : Date()[Symbol.toPrimitive](hint);
If hint is "string" or "default", ##toPrimitive tries to call the toString method. If the toString property does not exist, it tries to call the valueOf method and if the valueOf does not exist either, ##toPrimitive throws a TypeError.
If hint is "number", ##toPrimitive first tries to call valueOf, and if that fails, it calls toString.
var date = new Date(2017,1,1);
console.log('hint is "number":',+date + +date); // using unary plus operator
console.log('hint is "default":',date + date); // no hint (falls to hint == 'string')
console.log('hint is "string":',`${date + date}`); // using template literals
The + operator is the most confusing among all arithmetic operators. without a hint to convert the Date instance to a number, it acts as a string concatenator

Passing value into methods by dot prefixes

What I want to do should be incredibly simple but I suppose I'm not understanding the basic concepts and causing me more confusion than I'd like to admit.
I want to pass a value into a function by "chaining" it instead of passing the value as an argument.
I want to be able to do this:
var formattedDate = myDate.convertTime()
Instead of:
var formattedDate = convertTime(myDate);
In my search I've come across many examples of how to use call functions inside objects, but they always start with declaring a new object and calling the function inside that object that simply updates predefined variables like age, name, or location as in new Obj(name, age) which doesn't seem to be what I am looking for when applied to my situation. I'd really appreciate some guidance in the right direction.
Are you looking for something like following
String.prototype.myFunction = function() {
return this.toUpperCase();
}
var text = "sample text";
console.log(text.myFunction()); // calling user defined function
Fiddle: https://jsfiddle.net/mna7jxrs/
Update 1:
For example, We're passing a date string to convertTime() and it is converting it to UTC String
Date.prototype.convertTime = function() {
return this.toUTCString();
}
var date = new Date();
console.log(date); // Mon Oct 31 2016 11:56:57 GMT+0530 (India Standard Time)
console.log(date.convertTime()); // Mon, 31 Oct 2016 06:26:57 GMT
Fiddle: https://jsfiddle.net/doc7gL2g/
What you want to do is add a method to your object myDate.
Lets assume myDate is a date created by var myDate = new Date();.
If you want to add it only to the current object, you can do this way:
var myDate=new Date();
myDate.convertTime = function() {
console.log("I’m working on", this);
};
myDate.convertTime(); // -> see console
Now to make it more generic, you want to do something we call monkey patching:
Date.prototype.convertTime = function() {
console.log("I’m working on", this);
};
var myDate = new Date();
myDate.convertTime(); // -> see console
var myOtherDate = new Date();
myOtherDate.convertTime(); // -> see console
A working pen for the monkey patch: http://codepen.io/BuonOmo/pen/RGzYmz?editors=0012

Why is this function behaving weirdly

I have the following JavaScript as below
function getExpiryDate(contract) {
var expiryDay;
var contractType;
var today = new moment();
var today1 = new moment();
var x = myFunction(4, 3);
var abc =3;
var xyz= 4;
var c = myFunction(abc, xyz);
console.log("abc is: "+abc);
console.log("xyz is: "+xyz);
console.log(today1);
expiryDay = getlastDayofMonth('Thursday',today1);
console.log(today1); /* Why has the value of today changed? */
}
function getlastDayofMonth(dayName,date1) {
var endDate = date1.endOf('month');
var lastDayOfMonth = endDate.format('dddd');
var weekDayToFind = moment().day(dayName).weekday(); //change to searched day name
var searchDate = endDate; //now or change to any date
while (searchDate.weekday() !== weekDayToFind)
{
searchDate.subtract(1, 'days');
}
return searchDate;
}
function myFunction(a, b) {
return a * b; // Function returns the product of a and b
}
When I execute I get the following output.
expirydate.js:11 abc is: 3
expirydate.js:12 xyz is: 4
expirydate.js:13 Moment { _d: Wed Aug 31 2016 10:21:04 GMT+0530 }
expirydate.js:15 Moment { _d: Thu Aug 25 2016 23:59:59 GMT+0530 }
I am totally confused on why the value of today1 changes when it is used in the function.
Because in JS, objects are passing by reference and not by value. It means you are working on the same object.
To prevent this you have to clone date1 value in the function into a variable with a scope restricted to the function
This is happening because Moment is written in a mutable style of code. It's fairly surprising behavior. After reading through your code a few times I couldn't find any obvious problem until I looked at the Moment documentation.
The .endOf() method mutates the date, aka changes the date of the moment object you call it on:
Mutates the original moment by setting it to the end of a unit of time.
So when you call that method here:
var endDate = date1.endOf('month');
It's mutating date1, meaning it modifies date1 in place and changes its time. In fact, it looks like almost all of moment's methods mutate the moment object. There's a discussion on Github about why the API is designed poorly.
In terms of solving your specific problem, it's a personal style preference. I think it's undesirable to force the user to clone an object before passing it to a function. So I would clone the moment passed in inside the function:
function getlastDayofMonth(dayName,date1) {
var endDate = date1.clone().endOf('month');

Javascript JSON Date Deserialization

I am trying to deserialize a json object that has a javascript date in it. When JSON.stringify is called on the object, dates are serialized to strings that are not properly deserialized back to dates. I have attempted to deserialize the object using both the native browser implementation with chrome, IE, and FF and using jquery. Both give the some results. Here is the snippet:
var obj = {Date: new Date()};
var objSer = JSON.stringify(obj);
var objDeser = JSON.parse(objSer);
var objJqDeser = $.parseJSON(objSer);
function getYear(value){
try{
return value.getYear();
}
catch(err){
return err;
}
}
$("#orig").text("Orig Year: " + getYear(obj.Date));
$("#deser").text("Deser Year: " + getYear(objDeser.Date));
$("#jqDeser").text("JqDeser Year: " + getYear(objJqDeser.Date));
I want objDeser.Date to be a js date not a string. You can see this problem in action here: http://jsbin.com/unijud/24/edit. Is there any js libraries that can properly deserialize the dates when building the javascript object?
JSON.parse has a little-known second parameter: the 'reviver' function. This is used for precisely this purpose: to revive a date string into a Date object (or, hypothetically, any other kind of object you wanted to convert from string) during the initial parse.
There's an SO post about this, and here's a blog post that includes an implementation example and a function that will do property checking for a couple common date encodings (ISO & that weird .NET AJAX format), before parsing to a Date.
Here's the key function from that blog post, fwiw:
// JSON date deserializer
// use as the second, 'reviver' argument to JSON.parse();
if (window.JSON && !window.JSON.dateParser) {
var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
JSON.dateParser = function (key, value) {
// first, just make sure the property is a string:
if (typeof value === 'string') {
// then, use regex to see if it's an ISO-formatted string
var a = reISO.exec(value);
if (a) {
// if so, Date() can parse it:
return new Date(value);
}
// otherwise, see if it's a wacky Microsoft-format string:
a = reMsAjax.exec(value);
if (a) {
// and perform some jujitsu to make use of it:
var b = a[1].split(/[-+,.]/);
return new Date(b[0] ? +b[0] : 0 - +b[1]);
}
// here, you could insert any additional tests and parse instructions you like, for other date syntaxes...
}
// important: you need to return any values you're not parsing, or they die...
return value;
};
}
// use: JSON.parse(json,JSON.dateParser);
(There are lots of opinions about proper regexes for ISO 8601 dates. YMMV. Also, there's no particular reason to punch the function onto the global JSON object. You could store/reference it anywhere you like. )
I took #LastCoder advice and wrote a simple implementation. It seems to be doing what I wanted it to.
var jsonDates = {
dtrx2: /\d{4}-\d{2}-\d{2}/,
parse: function(obj){
var parsedObj = JSON.parse(obj);
return this.parseDates(parsedObj);
},
parseDates: function(obj){
// iterate properties
for(pName in obj){
// make sure the property is 'truthy'
if (obj[pName]){
var value = obj[pName];
// determine if the property is an array
if (Array.isArray(value)){
for(var ii = 0; ii < value.length; ii++){
this.parseDates(value[ii]);
}
}
// determine if the property is an object
else if (typeof(value) == "object"){
this.parseDates(value);
}
// determine if the property is a string containing a date
else if (typeof(value) == "string" && this.dtrx2.test(value)){
// parse and replace
obj[pName] = new Date(obj[pName]);
}
}
}
return obj;
}
};
A live example is available on jsbin. A reference is available on gist.
In order to represent dates using JavaScript, I found that JSON uses ISO 8601, a specific string format to encode dates as string. When I last checked though, there is not an official standard for what the date format should look like. The major browsers use ISO 8601 as the JSON Date encoding format.
So, dates are encoded as ISO 8601 strings and then used just like a regular strings when the JSON is serialized and deserialized.
That being said, ISO dates can be converted into JavaScript dates by use of the JavaScript Date constructor, which accepts a wide variety of inputs to construct a date, ISO 8601 being one of them.
Get todays date:
var curDate = new Date();
document.write(curDate); //Mon Feb 01 2016 12:57:12 GMT-0600 (Central Standard Time)
Parse it into a string:
var dateStr = JSON.parse(JSON.stringify(curDate));
document.write(dateStr);//2016-02-01T18:59:35.375Z
Then convert it back to a javascript date, using the constructor:
var date = new Date(dateStr);
document.write(date); //Mon Feb 01 2016 12:59:35 GMT-0600 (Central Standard Time)
The JSON spec does not include special formatting for dates. As such they are often serialized as a string, sometimes with special markings to indicate it should be treated as a Date object if the language supports them. As such, most (all?) browser-native JSON parsers can not round-trip a Date object properly.
There are several good libraries that help with this - I very much like MomentJS though I have used datejs in the past as well. You would just need to iterate over your objects and convert the proper fields to Date objects after they have been parsed.
I find it helpful to remember that the JSON format is much more restrictive than JavaScript object literal notation.
The JavaScript es5 is able to parse the date like 2018-04-03T22:00:00... in its default Date constructor. (e.g. new Date("2018-04-03T22:00:00...");.
For someone like me, who is searching for automatic date deserialization from JSON web responses, this could be handy.
/**
* Iterates over all entries of the input object and replace the string dates with the objects of {#link Date}.
*/
function fixDateObjects(responseBody) {
if (responseBody) {
const regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}/;
for (const [key, value] of Object.entries(responseBody)) {
const val = String(value);
if (val.startsWith('[object Object]')) {
fixDateObjects(value);
}
if (val.match(regex)) {
responseBody[key] = new Date(val);
}
}
}
}
Explanation: It iterates over all object entries of the JSON(responseBody) and replaces the string dates (matched by the given regex) with the new Date(str) objects.
Result: This brings you freedom of further processing. Now you have all date strings fully deserialized.
//usage
function parseBody(response) {
fixDateObjects(response);
console.log(response.someDate); // Mon Aug 30 2021 22:45:59 GMT+0200 (...)
// further processing
}
You could manually add all of the Date functions you require to the String.prototype.
String.prototype.getYear = function() {
return Date.parse(this).getYear();
};
var obj = {date: new Date()};
var dtObj = JSON.parse(JSON.stringify(obj));
console.log(dtObj.date.getYear());
Or you could override JSON.parse and have it loop through the result object looking for strings that match the time stamp regex and then convert them to Date objects.
var JSON_parse = JSON.parse;
JSON.parse = function(str) {
var res = JSON_parse(str);
findAndConvertStringsToDates(res);
return res;
}
EDIT Here's what I'd throw together for an implementation
(function() {
var jsonParse = JSON.parse;
var reDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/i;
function jsonDate(obj) {
var type = typeof(obj);
if(type == 'object') {
for(var p in obj)
if(obj.hasOwnProperty(p))
obj[p] = jsonDate(obj[p]);
return obj;
} else if(type == 'string' && reDate.test(obj)) {
return new Date(obj);
}
return obj;
}
JSON.parse = function(str) { return jsonDate(jsonParse(str)); }
})();
/*
* Tests
*/
var dt = JSON.parse(JSON.stringify({date: new Date()}));
console.log(typeof(dt.date));
console.log(JSON.parse(JSON.stringify(null)));
console.log(JSON.parse(JSON.stringify(123)));
console.log(JSON.parse(JSON.stringify("test")));
console.log(JSON.parse(JSON.stringify(new Date())));
console.log(JSON.parse(JSON.stringify([1,new Date(),2])));
console.log(JSON.parse(JSON.stringify({d: new Date(), d2: {d3: new Date(), d4: [0,new Date(),4]}})));

Categories

Resources