Heracles Resources vs JSON-LD Compaction - Enumerable Js Properties
In my previous post I presented the first incarnation of Heracles, the Hydra Core client library. While trying to replace my makeshift client I’d implemented for an in-house training project at PGS I quickly decided that I’m going to need a way to compact my resources. It wasn’t that hard but there was one simple hurdle to overcome.
TL;DR; with Heracles you can do this:
1 2 3 4 5 6 7 |
|
URI properties are a nuisance
Just as in heracles, in my proof of concept code I too mostly worked with expanded JSON-LD objects. This has the downside that any time I needed to access the properties full property identifiers must be used. Also it is not possible with Polymer to use the indexer notation for declarative data binding:
1 2 3 4 5 |
|
This is precisely what JSON-LD compaction algorithm is for. It translates URI keys in a compacted JSON object. This
translation is defined in a @context
object.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
There are many tricks up compaction’s sleeve, which can help turning ugly JSON-LD into a digestive form. Have a look at this presentation by Markus Manthaler for some more examples.
My code before
In my code I used compaction to get rid of long URI keys so that I can take advantage of Polymer’s data binding without verbose methods like computed properties or wrapping the object in a view model class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
This is simple, the jsonld.js library takes care of the heavy lifting and produces a compacted object which is data binding friendly.
Enter heracles
How is this relevant to the heracles library? In my previous post I showed the Operation
type (and other parts of the
ApiDocumentation classes) can be compacted so that working with them is easier.
Resources however are a little different. They are always returned expanded and thus should be ready for being compacted.
I was surprised to see that jsonld.promises.compact
throws a stack overflow error. The reason is that JSON-LD algorithms
are not designed to work with cyclical object graphs. It simply loops until the call stack runs out.
The Resource class
In my code I have this PartialCollectionView
class (excerpt):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
See the collection
getter? This is where I had a cycle (collection -> view -> collection …). There was also another
cycle inside the apiDocumentation
getter in the base Resource
class. There are actually two thing going on here. The
first and obvious culprit is the private field. Of course this is just TypeScript sugar, because it will become just a
typical field in the compiled JavaScript. JavaScript has no such notion of private members.
Solution
The first step was to get rid of the field. There is no perfect way to do that but a friend of mine sent me this post,
which presents the use of WeakMap
as a possible solution. With that I changed my code so that it no longer contains
unwanted fields. (actual code is actually a little different but you get the drift)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Unfortunately the compaction algorithm still entered the vicious cycle and failed. Why is that? Because enumerable
properties. jsonld.js iterates over the object using simple for (var i in obj)
loop, which also
returns all getters by default. One way is to use the native Object.defineProperty
method instead of ES6 get x()
syntax but it breaks TypeScript code analysis and generally smells. There is a better way though.
Solution part two
Luckily TypeScript has the decorators and there is a decorator, which does precisely what I wanted. Instead of writing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
I can simply install the core-decorators package from jspm (npm) and
decorate the property with @nonenumerable
1
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
One Caveat
Of course this will still fail if there are actual cycles in the object graph. I’m hoping though that it won’t be the
case all too often. And for the rare occasion a library like circular-json can be used as suggested in this github
issue. It will make sure that there are no reference cycles. Unfortunately it is a only replacement for
JSON.stringify
and so to use it with jsonld.js it’s necessary to deserialize and serialize every time:
1 2 3 4 5 |
|
This is because jsonld.js wants to treat a string parameter as URI.
Please let me know if there is a better way for handling cyclical objects…