My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label good. Show all posts
Showing posts with label good. Show all posts

Sunday, August 19, 2012

Why JSON Won ... And Is Good As It Is

I keep seeing developers complaining about different things with JSON protocol and don't get me wrong, I've been the first one trying to implement any sort of alternative starting from JSOMON and many others ... OK?

Well, after so many years of client/server development is not that I've given up on thinking "something could be better or different", is just that I have learned on my skin all reasons JSON is damn good as it is, and here just a few of these reasons.

Reliable Serialization ?

No, 'cause YAGNI. There are few serialization processes I know that kinda work as expected and since ever, PHP serialize is a good example.
Recursion is not a problem, is part of the serialization process to solve it, as well as classes together with protected and private properties. You can save almost any object within its state, even if this object won't be, as reference, the same you serialized .. and I would say: of course!
There are also two handy methods, __sleep and __wakeup, able to let you save an object state in a meaningful way and retrieve it back or perform some action during deserialization.

Are these things available in JSON ? Thanks gosh NO! JSON should not take care of recursive objects ... or better, it's freaking OK if it's not compatible 'cause recursion is a developer matter or issue, not a protocol one!
All JSON can do is to provide a way to intercept serialization so that any object with a .toJSON() method can return it's own state and any time JSON.parse() is performed, it could bring back, if truly necessary, its recursive property.

So, at the end of the day, JSON implementations might provide already a similar way to __sleep and __wakeup objects but it should be the JSON string owner, the service, the developer, to take care of these problems, and simply because ....

Universal Compatibility

JSON is a protocol and as a protocol it should be as compatible as possible with all languages, not only those C like or others with similar comments ... there won't be comments ever in JSON, 'cause the moment you need comments, you don't need a transport protocol 'cause programming languages have always ignored developers comments ... and also, for compatibility reasons, not all programming languages would like to have // or /* */ or even # as inline or multiline comment ... why would they?

Specially in .NET world most of documentation is written in a pseudo XML, can you imagine you bothering yourself to write such redundant markup language to write something often ignored by developers ? Would you like to have that "crap" as part of the data you are sending or receiving via JSON as part of that protocol? I personally don't ... thanks! 'cause I believe a transport protocol should be as compact as possible and without problems.
Here JSON wins once again 'cause it's compatible, with its few universal rules, with basically everything.

Different Environments

This is the best goal ever reached from a protocol, the fact that every programming language can represent somehow what JSON transports.
Lists, Arrays, Dictionaries, Objects, Maps, Hashes, call them as you want, these are the most used and cross language entities we all deal with on daily bases, together with booleans, strings, and numbers.

OK, OK, specially numbers are quite generic but you might admit that the world is still OK with a generic Int32 or Float32 number and with 64bits compatible environments, these numbers could be of a different type but only if you will never deal with 32 bits environments ... make you choice ... you want a truly big number? Go for it, and loose the possibility to "talk" with any other 32 bit env ... not a big deal if you own your data, kinda pointless memory and CPU consumption if you deserialize everything as 64 bits ... but I am pretty sure you know what you are doing so ... JSON is good in that case too.

No Classes

And again thanks gosh! You don't want a protocol that deals with classes, trust me, 'cause you cannot write a class in all possible programming languages, can you? If you can, even in those programming languages where classes never existed 'cause classes are simply an abstract concept represented by the word "class" but representable in billion ways with other languages (e.g. via just objects in JavaScript).
Classes and namespaces issues, if you want, are there in any case.
The good part of JSON, once again, is the ability to intercept serialize and unserialize process so that if you like to send instances, rather than just objects, you can use all tools provided by the implementation, and I am showing in this case a JavaScript example;

function MyClass() {
// doesn't matter what we do here
// for post purpose, we do something
this.initialized = true;
}
MyClass.prototype.toJSON = function () {
this.__class__ = "window.MyClass";
return this;
};

var myClassObject = JSON.stringify(new MyClass);
// "{"initialized":true,"__class__":"window.MyClass"}"

Once we send this serialized version of our instance to any other client, the .__class__ property could be ignored or simply used to understand what kind of object was it.

Still in JavaScript, we can deserialize easily the string in such way:

function myReviver(key, value) {
if (!key) {
var instance = myReviver.instance;
delete instance.__class__;
delete myReviver.instance;
return instance;
}
if (key == "__class__") {
myReviver.instance = myReviver.createInstance(
this, this.__class__
);
}
return value;
}

myReviver.createInstance = "__proto__" in {} ?
function (obj, className) {
obj.__proto__ = myReviver.getPrototype(className);
return obj;
} :
function(Bridge) {
return function (obj, className) {
Bridge.prototype = myReviver.getPrototype(className);
return new Bridge(obj);
};
}(function(obj){
for (var key in obj) this[key] = obj[key];
})
;

myReviver.getPrototype = function (global) {
return function (className) {
for (var
Class = global,
nmsp = className.split("."),
i = 0; i < nmsp.length; i++
) {
// simply throws errors if does not exists
Class = Class[nmsp[i]];
}
return Class.prototype;
};
}(this);

JSON.parse(myClassObject, myReviver) instanceof MyClass;
// true

Just imagine that __class__ could be any property name, prefixed as @class could be, or with your own namespace value @my.name.Space ... so no conflicts if more than a JSON user is performing same operations, isn't it?

Simulating __wakeup Call

Since last example is about __sleep, at least in JavaScript easily implemented through .toJSON() method, you might decide to implement a __wakeup mechanism and here what you could add in the proposed revival method:

function myReviver(key, value) {
if (!key) {
var instance = myReviver.instance;
delete instance.__class__;
delete myReviver.instance;
// this is basically last call before the return
// if __wakeup was set during serialization
if (instance.__wakeup) {
// we can remove the prototype shadowing
delete instance.__wakeup;
// and invoke it
instance.__wakeup();
}
return instance;
}
if (key == "__class__") {
myReviver.instance = myReviver.createInstance(
this, this.__class__
);
}
return value;
}

Confused ? Oh well, it's easier than it looks like ...

// JSON cannot bring functions
// a prototype can have methods, of course!
MyClass.prototype.__wakeup = function () {
// do what you need to do here
alert("Good Morning!");
};

// slightly modified toJSON method
MyClass.prototype.toJSON = function () {
this.__class__ = "window.MyClass";
// add __wakeup own property
this.__wakeup = true;
return this;
};

Once again, any other environment can understand what's traveling in therms of data, but we can recreate a proper instance whenever we want.

How To Serialize

This is a good question you should ask yourself. Do you want to obtain exactly the same object once unserialized? Is that important for the purpose of your application? Yes? Follow my examples ... no? Don't bother, the less you preprocess in both serializing and unserializing objects, the faster, easier, slimmer, will be the data.

If you use weird objects and you expect your own thing to happen ... just use tools you have to intercept before and after JSON serialization and put there everything you want, otherwise just try to deal with things that any other language could understand or you risk to think JSON is your own protocol that's missing this or that, while you are probably, and simply, overcomplicating whatever you are doing.

You Own Your Logic

Last chapter simply demonstrates that with a tiny effort we can achieve basically everything we want to ... and the cool part is that JSON, as it is, does not limit us to create more complex structures to pass once stringified or recreate once parsed and this is the beauty of this protocol so please, if you think there's something missing, think twice before proposing yet another JSON alternative: it works, everywhere, properly, and it's a protocol, not a JS protocol, not a X language protocol ... just, a bloody, protocol!

Thanks for your patience

Friday, December 04, 2009

with: Some Good Example

OK, I have said my last post would have been the last one about the with statement ... well, before I close the argument, I would like to show a couple of common good examples.

Solve References Problem

This is what somebody defined
a tiny masterpiece


with({o:myreference}){
o.doStuff();
o.var1 = "whatever";
// etc etc ...
};

Above example annihilate every blame about the not sure if that is the var I meant to use since there is an explicit reference as is, as example, in Python programming language (until version 3.11)

// Python
with open("x.txt") as f:
data = f.read()

// JavaScript equivalent
with({f:/* as */open("x.txt")})
data = f.read()


Memory Safe Operations

Another recycled example before I show more interesting stuff ... apparently the whole problem is about write silly code inside with, as if everybody has to define variables or assign stuff inside this statement. Hilariously, I have basically never assigned anything inside a with in 8 years of ECMAScript programming ... does it tell you anything?
A clever usage could be the one to beat whatever compressor and make code less redundant:

with(document)
with(documentElement)
insertBefore(
createElement("script"),
firstChild
)
.text = "alert(1)"
;

Try to do more in less characters considering I am not creating a single reference at all, as I am not creating a useless closure just to justify references creation ... done? Now use whatever compiler/compresor/minifier you want and try to obtain less than 99 bytes, respecting the same cross browser, clean, memory safe and leaks free nature ...

Discover Named Arguments

A too much common approach in JavaScript is the one to create an inline executed closure to perform some task being sure local variables won't disturb outer or global scope.

(function(obj, collection, callback){
// a common argument normalizer
if(!collection)
collection = []
;
// a loop, 'cause we don't want to perform it
// in the global scope
for(var i = 0, length = collection.length, tmp; i < length; ++i){
// callback returns an object only under certain conditions
tmp = callback.call(obj, collection[i]);
if(tmp){
tmp.doStuff();
collection[i] = tmp;
};
};
})();

Guys, above example is daily basis JavaScript programming
  1. create a closure to avoid outer scope properties declaration

  2. normalize, if necessary, unexpected/undefined/empty arguments

  3. define internal closure variables with or without a value

  4. perform some operation

  5. exit from the executed closure (implicit)
Now, consider we all love to call methods or functions via configuration objects

ajax({
url:somestring,
params:someObject,
success:callback,
failure:shenanigans
});

A configuration object is semantic, it allows us to perform some task inline, like ternary operator assignment (and I bet since somebody does not know ternary operator ES5 team will decide it's evil ...) and make callback calls more friendly ... but what we have always been envious about Python is the ability to use named arguments defining defaults, if necessary, for each value.
In JavaScript we can send named arguments, via a configuration object, but we cannot define arguments defaults ... but in one shot, we could emulate everything defining both defaults and, since we need to define them in any case, local variables ... don't you follow me?

// zero closure named arguments example
with({
obj:myObject,
// inline defaults, if necessary
collection:collection || [],
callback:myCaseAnalyzer,
// local variables
i:0,
length:collection ? collection.length : 0,
// local undefined variable
tmp:null
}){
for(;i < length; ++i){
tmp = callback.call(obj, collection[i]);
if(tmp){
tmp.doStuff();
collection[i] = tmp;
};
};
};

Same behavior, zero ambiguity, if we call another variable name is because we need that variable from the outer scope. If we create a variable we are doing wrong because inside a closure we would have used var, while with this approach we need simply to define, rather than var, a property with null value.
Moreover, if tomorrow we need an extra argument via with statement all we need to do is to define it in whatever position we like, without being worried about arguments order (let's say bad design) since there is no order, except the one we prefer, in object properties.

Global Clear Ajax Call

This is the last example for this post, just ajax when we need it, whenever we are, without the classic reference and the missed "this" feature in the onreadystatechange function:

with(this.XMLHttpRequest ?
new XMLHttpRequest :
new ActiveXObject("Microsoft.XMLHTTP")
){
open("get", "?ajax=true", true);
onreadystatechange = function(){
if(readyState === 4)
// do stuff with the responseText/XML
alert(responseText)
;
};
send(null);
};


Global Paradox

The funniest part ever about this with statement is that basically every JavaScript has an implicit global object scope.
In few words every time a script is executed is basically the same of a massive with

with(window){
// same as window.alert(123);
alert(123);

// same as window.onload = function(){};
onload = function(){
};

(function(){
// same as new window.XMLHttpRequest
var xhr = new XMLHttpRequest;
})();

// same as window.String.fromCharCode
String.fromCharCode(1);
}

Of course being the global object the last possible outerscope, if we define a variable directly or via var in that scope we will attach this property to the window one.
In few words the with is the ABC of JavaScript but again, some clever guy decided that since somebody cannot understand closures, then extended closures, the basic principle of the programming language itself has to be removed ... right?

Think about it, if ES5 will remove even the with statement making this unavailable in the "use strict" future proof declaration, JavaScript won't be the one that created all its success around its closure and prototype based nature.

Think!