Thursday 11 April 2013

Plutarch (via Andrew Myers) teaches me something about struct keys

G'day
I chat to Andrew Myers a bit on Twitter (OK, well it goes out to the entire Twitter universe, but you know what I mean). And a few weeks ago he flicked through some interesting code to me. I've been meaning to write this up for three weeks now, but other stuff seems to keep cropping up that I "need" to write up first. Anyway, I've just looked at his repro case and it's an interesting one. And the code speaks for itself (thus making this a very easy article to write):



<p>It's okay to use a dot in a struct key, as long as that is the end of the line, eg.</p>

<cfset plutarch=StructNew()>
<cfset plutarch['lives.antony']="Antony's grandfather was the orator Antonius, who joined the party of Sulla...">
<cfset plutarch['lives.brutus']="Marcus Brutus was a descendant of that Junius Brutus whose bronze statue, with...">
<cfset plutarch['lives.caesar']="The wife of Caesar was Cornelia, the daughter of the Cinna...">
<cfset plutarch['lives.caesar.notes']=StructNew()>
<cfset plutarch['lives.caesar.notes'].one = "Many think that opening paragraphs of this Life, describing the birth and boyhood of Caesar, have been lost.">

<p>It seems all these keys work independently of each other - ie. a dot is a valid character in a struct key.  But what happens now if we assign something to 'lives'?</p>

<cfdump var="#plutarch#">

<cfset plutarch['lives'] = "A series of biographies of famous Greeks and Romans, arranged in pairs to illuminate their common moral virtues and vices...">

<cfdump var="#plutarch#">

<p>Still good....</p>
<p>Or is it?  What if what we want to assign is another struct?</p>

<cfset plutarch['lives'] = StructNew()>

<cfdump var="#plutarch#">

<p>Ooops!</p>


The "oops" is because this is what happens on ColdFusion (CF 9.0.2 and CF 10.0.9):

It's okay to use a dot in a struct key, as long as that is the end of the line, eg.
It seems all these keys work independently of each other - ie. a dot is a valid character in a struct key. But what happens now if we assign something to 'lives'?
struct
lives.antonyAntony's grandfather was the orator Antonius, who joined the party of Sulla...
lives.brutusMarcus Brutus was a descendant of that Junius Brutus whose bronze statue, with...
lives.caesarThe wife of Caesar was Cornelia, the daughter of the Cinna...
lives.caesar.notes
struct
ONEMany think that opening paragraphs of this Life, describing the birth and boyhood of Caesar, have been lost.
struct
livesA series of biographies of famous Greeks and Romans, arranged in pairs to illuminate their common moral virtues and vices...
lives.antonyAntony's grandfather was the orator Antonius, who joined the party of Sulla...
lives.brutusMarcus Brutus was a descendant of that Junius Brutus whose bronze statue, with...
lives.caesarThe wife of Caesar was Cornelia, the daughter of the Cinna...
lives.caesar.notes
struct
ONEMany think that opening paragraphs of this Life, describing the birth and boyhood of Caesar, have been lost.
Still good....
Or is it? What if what we want to assign is another struct?
struct
lives
struct [empty]
lives.antonyundefined
lives.brutusundefined
lives.caesarundefined
lives.caesar.notesundefined
Ooops!

Oops indeed. Where's all the data gone?

I also ran it on Railo 4.1 and OpenBD 3.0, and they both work fine. On a whim I also ran it on CF 8.0.1, and it runs fine on that too. Here's what should have happened (this is the CF8 output):

struct
lives
struct [empty]
lives.antonyAntony's grandfather was the orator Antonius, who joined the party of Sulla...
lives.brutusMarcus Brutus was a descendant of that Junius Brutus whose bronze statue, with...
lives.caesarThe wife of Caesar was Cornelia, the daughter of the Cinna...
lives.caesar.notes
struct
ONEMany think that opening paragraphs of this Life, describing the birth and boyhood of Caesar, have been lost.

This is the correct output.

I tried to find a bug for this, but drew a blank. I'll raise one now: 3539842.

Update
As tipped by Matt, the bug is with <cfdump>, not with struct support. I added this code to the bottom of the file, and its output follows:

<cfloop item="topKey" collection="#plutarch#">
    <cfoutput>
        [#topKey#]:
        <cfif isSimpleValue(plutarch[topKey])>
            [#plutarch[topKey]#]<br />
        <cfelse>
            <cfloop item="innerKey" collection="#plutarch[topKey]#">
                <cfoutput>
                    
&nbsp;&nbsp;&nbsp;&nbsp;[#innerKey#]:
                    [#plutarch[topKey][innerKey]#]<br />
                </cfoutput>
            </cfloop>
        </cfif>
    </cfoutput>
</cfloop>

[lives.caesar.notes]: 
    [ONE]: [Many think that opening paragraphs of this Life, describing the birth and boyhood of Caesar, have been lost.]
[lives.antony]: [Antony's grandfather was the orator Antonius, who joined the party of Sulla...]
[lives.caesar]: [The wife of Caesar was Cornelia, the daughter of the Cinna...]
[lives.brutus]: [Marcus Brutus was a descendant of that Junius Brutus whose bronze statue, with...]
[lives]:

So the values are there. It's just that <cfdump> must go about things a curious way when it comes to traverse the object it's dumping, and gets confused. So that's heartening. Mostly. Kinda.

Andrew, thanks for taking the time to write this up, and pass it on to me. The beer's on me next time we're in the same place at the same time.

Righto.

--
Adam