Wednesday 16 January 2013

Random (unsuccessful) experiment: recursive JSON

G'day:
This quick experiment arose from a comment on Twitter this morning:

@daccf @ntunney the worst part is that relationships don't get serialized either. Apparently recursion is too hard for them? 
(Todd Sharp ‏@cfsilence)
This was a reaction to a bug about serialising entities which was under discussion.


This got me thinking... the predefined (notional / by convention ~) data-typing in JSON kinda lets itself down a bit here. What one needs to be able to do is to represent a reference to an object somehow. This is mentioned in the Wikipedia article on JSON, and I'll read the references a bit later.

On a whim, I decided to see how CF deals with recursion in references, and the summary is: badly.

Here's some code that demonstrates:

<cfscript>
    colours = [];
    colours[1] = {
        maori = "mangu",
        english    = "black"
    };
    colours[2] = {
        maori = "pango",
        english    = "black"
    };

    colours[1].synonyms = [colours[2]];
    colours[2].synonyms = [colours[1]];

    // writeDump(colours); // don't do this ;-)

    serialised = serializeJson(colours);
    writeDump(serialised);
</cfscript>
<cfwddx action="cfml2wddx" input="#colours#" output="wddx">
<cfdump var="#wddx#">

Here we have a "colour dictionary" which has a coupla different words for black, which are cross-referenced to each other as synonyms.  A completely legit thing to want to do.

If I let the commented-out writeDump() run, it runs and runs and runs and then must reach some internal failsafe and stops. So it's not recursion-aware, really. The output is very pretty though :-)

The serializeJson() call just faceplants with a 500 error.

And the WDDX call doesn't error, but it does halt processing (which is worse than raising a 500, I think).

I have now modified the code to use objects instead of structs:

// Colour.cfc
component {

    public Colour function init (required string maori, required string english){
        this.maori        = arguments.maori;
        this.english    = arguments.english;
        return this;
    }

}

<cfscript>
    colours = [];
    colours[1] = new Colour(
        maori = "mangu",
        english    = "black"
    );
    colours[2] = new Colour(
        maori = "pango",
        english    = "black"
    );

    colours[1].synonyms = [colours[2]];
    colours[2].synonyms = [colours[1]];

    writeDump(colours);
    exit;

    serialised = serializeJson(colours);
    writeDump(serialised);
    throw "After serializeJson()";
</cfscript>
<cfwddx action="cfml2wddx" input="#colours#" output="wddx">
<cfdump var="#wddx#">
<cfthrow message="After WDDX">

Here the dump works nicely:

array
1
component cf.datatypes.json.Colour
SYNONYMS
array
1
component cf.datatypes.json.Colour
ENGLISHblack
MAORIpango
SYNONYMS
array
1[see cfc1 for Colour details]
METHODS
MAORImangu
ENGLISHblack
METHODS
2[see cfc2 for Colour details]

So CF is aware of what a reference is.

However if I remove the exit, and allow the serializeJson() to run, it dies before it gets to the throw. And similarly if I comment-out the serializeJson() call and try WDDXing it instead... again, processing halts without error. This is a bit crap.

I'm going to leave this one up in the air a bit as it's 9am and I need to start work, but at some stage I'll continue to read up on JSON and dealing with references, and think some more about how nested entities should be serialised.

This is the sort of thing that Adobe ought to have already done. That they didn't do it could have two explanations I can think of, neither of which sit well with me:
  • it didn't even occur to them;
  • it did occur to them, but they too-hard-basketed or just shrugged it off.
But anyway, no point in dwelling on that I guess.

Now it's 9:10am. Oops. Best I prep for the meeting I have in 5min...

--
Adam