URL Object Notation: A better JSON for URLs
blog.vjeux.com
blog.vjeux.com
_foo_bar=Something&baz=Else
could parse as either of: {foo:{bar:"Something",baz:"Else"}}
{foo:{bar:"Something"}, baz:"Else"}
which seems to ruin things. You need a character to end an object, maybe ; or & would work.Also, I don't like using : in query strings as a separator since that makes it a bit ugly and not quite a query string. How about using = for both and using say ! to identify non-strings, and @item@item@item for arrays?
So your example becomes:
_user_name=Bob%20Smith&age=!47&sex=M&dob=5/12/1956&pastimes=@golf@opera@poker@rap&;children_bobby_age=!12&sex=M&;sally_age=!8&sex=F> could parse as either of:
> {foo:{bar:"Something",baz:"Else"}}
> {foo:{bar:"Something"}, baz:"Else"}
I don't think this is right, or at least, it shouldn't be right. I think _foo_bar=Something&baz=Else should only parse as {foo:{bar:"Something"}, baz:"Else"}.
{foo:{bar:"Something",baz:"Else"}} should be _foo_bar=Something&_foo_baz=Else
It should be possible for the writer of the article to fix this without major changes.
This makes sense, but is not what I or the author was suggesting. Note that it is somewhat repetitive (foo appears twice). In contrast the new suggestion simply has an possible ; to disambiguate these possibilities.
@item1@item2@item3 is a really good idea. It's added :)
_table_achievement_column=instance&ascending:true
is less clear than: table[achievement][column]=instance&table[achievement][ascending]=true
which also has the benefit of being parsed properly by most web frameworks.Which is way too verbose than some sort of
table[achievement[column=instance&ascending=true]]I find it better to have less readable output as long as there is a win in term of length. Because no one likes big urls.
Also, what is less visible in URLON is the object structure. But usually when you want to edit something, you are only interested in changing the values, not the structure itself.
col=instance&asc=1
Or even better: c=instance&a=1
:) URLON.stringify({c: 'instance', a: 1}) == '_c=instance&a:1'
We also benefit from the typing: 1 is a number an not a string :) URLON.stringify({c: 'instance', a: '1'}) == '_c=instance&a:1'
as well, right?You can actually run this on the blog page URLON.stringify({c: 'instance', a: '1'}) == '_c=instance&a:1' It will return false
First of all, in my understanding URLON is primarily made for URI fragments. No browser would send a URLON-encoded data, and if you're doing AJAX, then URLs are barely a concern.
While the JSON is overly-verbose, the URLON is way too human-unfriendly:
JSON: {"table":{"achievement":{"ascending":true,"column":"instance"}}}
URLON: _table_achievement_ascending:true&column=instance
Humans suck at parsing grammars. We even suck with deeply nested parentheses (that's why editors highlight matching parens), and counting underscores is way more counter-intuitive.Oh, and key names with underscores will look ugly.
If one'd want to be "URLish", and concise, he'd write something like
table[achievement[ascending=true&column=instance]]
While not perfect, at least, a human could understand what's going on from a first glance. _table_achievement_column=instance&ascending:true
my immediate reaction is “so there are two parameters, one called ‘table_achievement_column’, and one called ‘ascending’... wait... why isn’t there an equals sign between ‘ascending’ and ‘true’?” The syntax is just similar enough to query-string syntax that it can mislead to someone trying to parse it by eye. And if you’re trying to pass complicated recursive structures (the kind of structures that JSON was invented to describe) through the URL, they’re not going to be parseable by eye in any format.Edit: I've added Rison to the list of translations in the article. It is far more readable than URLON but I find that it doesn't feel like it's a part of an url.
http://example.com/service?query=q:'*',start:10,count:10&pretty=false
Looks pretty natural to me.Should it?
I thus think that to succeed this approach needs some support on the server side to convert the notation into an object that can be interrogated from code. OK, that would need to be different for each server runtime but things like this tend to pick up support fairly quickly.
I use JSON to communicate with ASP.NET web services because the .NET runtime provides a great de-serializer that can convert the JSON directly to one of my server side classes and vice versa
You can even do fun things like URLON -> Javascript Object -> JSON if you want to. The fact that it is 100% compatible with JSON is a great benefit.
As for my use, the URLON is in the hash part of the URL (after the #) so everything in running in the client. This is useful to give URLs that hold the current state of the page.
If you need to store page state RESTfully, I would suggest you create a page state resource and PUT your state there. If you need a page's state, ask for it by name. This also has the benefit of keeping URLs shorter and cleaner.
http://db.mmo-champion.com/items/#table__search_results_item...
I want to store: page number, sorted-column, reverse.
POST to /items/pageState to get a handle - this could be a randomly generated small sequence of characters. A response will contain a Location: header with a URL: /items/pageState/aA1 (the aA1 is just an example for this description - each user would get an unused sequence of characters)
Anytime the user changes the state of the page, PUT the page number, sorted-column, and reverse fields to /items/pageState/aA1.
Now, the url of the items page to http://db.mmo-champion.com/items/?pageState=aA1. When that page loads, the JS will make an Ajax GET request to /items/pageState/aA1 to fetch the state of the page, and re-render it appropriately.
If you're concerned about speed, well, don't be. Add support for eTags on /items/pageState, and while the state is unchanged, that data will be fetched from browser cache instead of the network.
Also, your solution adds a lot of extra server calls. The goal of client-side sorting & pagination is to avoid those. I don't want to get them back just for the sake of being RESTful.
My solution does not have to add a lot of extra server calls - it all depends on the caching strategy you choose. For example, if you use maxAge, you would only be adding one extra request per new state created, or per new fetch; all subsequent fetches for the same state would automatically pull from the browser cache.
To satisfy the linking requirement, I would redesign my solution to just POST the state to /items/pageState, and get back a URI that represents only that state (which can be shared across all users). Combine this with the maxAge, and yes, you would have to make more requests, but the extra overhead would still be way less significant than adding a link to an image on every page (especially in terms of bandwidth)
But, chacun à son goût! Everyone prefers their own tradeoffs :)
Also, this is really meant for web client apps that are maintaining their state in the URL (that crazy AJAX thing). The app would parse it and make whatever JSON requests are needed with the server.
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hyperte...
I tried `URLON.stringify(window)` and nothing happened, no error or return value (bad sign) so hit it a few more times, saw a couple stack overflow errors, and the tab crashed.
If possible it should definitely throw a RangeError instead of attempting to stringify a circular structure.
https://github.com/douglascrockford/JSON-js/blob/master/cycl...
As the only way to know if an object has already been visited is in linear time in Javascript, it makes cycle detection a O(n^2) process. This is way too costly to add by default unfortunately.
Perhaps a way to hack-fix this is to set a parameter for maxRecursion, to prevent the errors - seems like a pretty solid way of preventing endless loops (RangeErrors) but is probably something that should be off-by-default and switched on globally via a setting or once via a parameter...
I can add it and submit a pull-request for your consideration if you like?
I've added a jsPerf to see how bad it performs. For small objects it's reasonable but if the object has 10 000 elements the Javascript test will take 1 second, which is not acceptable.
I'd rather give people a detection cycle detection library if their input may be circular.
Here is an example:
user:name=Bob,age#=47,sex=M,dob%=5-12-1956;pastimes*:golf,opera,poker,rap;children:bobby:age#=12,sex=M;sally:age#=8,sex=FIn my understanding, most of time there's an occurence of "age=47", it actually means {"age": 47}, not {"age": "47"}. But if we'd want to represent the latter, we could easily do it with "age*=47".
So it'll be something like:
user:name=Bob,age=47,sex=M,dob=1956-12-05;pastimes:golf,opera,poker,rap;children:bobby:age=12,sex=M;sally:age=8,sex=FLove the idea.
https://github.com/vjeux/URLON/blob/master/urlon.js
If not, someone need to write a URLON library for your language.
http://db.mmo-champion.com/items/2/#table__search_results_it...
JSON:
{"user":{"name":"Bob Smith","age":47,"sex":"M","dob":"5-12-1956"},"pastimes":["golf","opera","poker","rap"],"children":{"bobby":{"age":12,"sex":"M"},"sally":{"age":8,"sex":"F"}}}
Base64: eyJ1c2VyIjp7Im5hbWUiOiJCb2IgU21pdGgiLCJhZ2UiOjQ3LCJzZXgiOiJNIiwiZG9iIjoiNS0xMi0xOTU2In0sInBhc3RpbWVzIjoNClsiZ29sZiIsIm9wZXJhIiwicG9rZXIiLCJyYXAiXSwiY2hpbGRyZW4iOnsiYm9iYnkiOnsiYWdlIjoxMiwic2V4IjoiTSJ9LA0KInNhbGx5Ijp7ImFnZSI6OCwic2V4IjoiRiJ9fX0=
JSON + URIEncode %7B%22user%22:%7B%22name%22:%22Bob%20Smith%22,%22age%22:47,%22sex%22:%22M%22,%22dob%22:%225-12-1956%22%7D,%22pastimes%22:%5B%22golf%22,%22opera%22,%22poker%22,%22rap%22%5D,%22children%22:%7B%22bobby%22:%7B%22age%22:12,%22sex%22:%22M%22%7D,%22sally%22:%7B%22age%22:8,%22sex%22:%22F%22%7D%7D%7D
URLON _user_name=Bob%20Smith&age:47&sex=M&dob=5-121956;&pastimes@=golf@=opera@=poker@=rap;&children_bobby_age:12&sex=M;&sally_age:8&sex=F >>> from base64 import b64decode
>>> from json import loads, dumps
>>> print dumps(loads(b64decode('eyJ1c2VyIjp7Im5hbWUiOiJCb2IgU21pdGgiLCJhZ2UiOjQ3LCJzZXgiOiJNIiwiZG9iIjoiNS0xMi0xOTU2In0sInBhc3RpbWVzIjoNClsiZ29sZiIsIm9wZXJhIiwicG9rZXIiLCJyYXAiXSwiY2hpbGRyZW4iOnsiYm9iYnkiOnsiYWdlIjoxMiwic2V4IjoiTSJ9LA0KInNhbGx5Ijp7ImFnZSI6OCwic2V4IjoiRiJ9fX0=')), indent=2)
{
"pastimes": [
"golf",
"opera",
"poker",
"rap"
],
## [etc.]
} http://db.mmo-champion.com/items/#eyJ1c2VyIjp7Im5hbWUiO
iJCb2IgU21pdGgiLCJhZ2UiOjQ3LCJzZXgiOiJNIiwiZG9iIjoiNS0x
Mi0xOTU2In0sInBhc3RpbWVzIjoNClsiZ29sZiIsIm9wZXJhIiwicG9
rZXIiLCJyYXAiXSwiY2hpbGRyZW4iOnsiYm9iYnkiOnsiYWdlIjoxMi
wic2V4IjoiTSJ9LA0KInNhbGx5Ijp7ImFnZSI6OCwic2V4IjoiRiJ9f
X0(I assume you have some reason for not just passing a session key in the URL and keeping all the relevant state on the server.)
json | gzip | base64 saves another 33 bytes :)
and you already know urlon is the shortest