Number of elements in list during json preprocessing

0 votes
asked Nov 6 in Wanted features by dragondive (440 points)

Please refer this example code:

@startuml
!$foo = { "company": "Skynet", "employees" : [
  {"name" : "alice", "salary": 100 },
  {"name" : "bob", "salary": 50} ]
}
start
!$index=0
:The salary of <u>$foo.employees[$index].name</u> is <u>$foo.employees[$index].salary</u>;
!$index=1
:The salary of <u>$foo.employees[$index].name</u> is <u>$foo.employees[$index].salary</u>;
@enduml

I want to "loop" through the list using the $index. But clearly, that requires to know the number of elements in the list. In my "real" usecase, I cannot use a foreach loop for this, I have to do it using index.

Is it possible to do this? If not, can it be added as a new feature? 

3 Answers

0 votes
answered Nov 6 by plantuml (262,900 points)

Not perfect, but you can have this.

@startmindmap
!$foo = { "company": "Skynet", "employees" : [
  {"name" : "alice", "salary": 100 },
  {"name" : "bob", "salary": 50} ]
}

!$index=1

* The salary of  
!foreach $emp in $foo.employees
  ** **$index** 
  *** **$emp.name** 
  **** is 
  ***** **$emp.salary**
  !$index = $index+1
!endfor
@endmindmap

Does it help ?

commented Nov 6 by dragondive (440 points)
Thanks, this does help, but now I realized, my original usecase is a lot more complicated than the simplified example (that I took from the document anyway). I described it as a comment to Martin's answer: https://forum.plantuml.net/14901/number-of-elements-in-list-during-json-preprocessing?show=14910#c14910

I think I should still be able to "hack" it using this approach, but it's mostly a "workaround" for me because my original usecase was to "parametrize" the json data attribute in the preprocessor code. I decided to still ask this question as it may be helpful for others.
0 votes
answered Nov 6 by Martin (5,640 points)

You need to explain your constraint for not using foreach better.  But have you considered using foreach just to set the max index? 

E.g. 

@startuml
!$foo = { "company": "Skynet", "employees" : [
  {"name" : "alice", "salary": 100 },
  {"name" : "bob", "salary": 50} ]
}
start
!$max=0
!foreach $entry in $foo.employees
!$max=$max+1
!endfor

!$index=0
!while $index < $max
:The salary of <u>$foo.employees[$index].name</u> is <u>$foo.employees[$index].salary</u>;
!$index=$index+1
!endwhile
@enduml

commented Nov 6 by dragondive (440 points)

Sure, my actual goal is to "parametrize" the attribute that will be used to generate the diagram, as described here: Specifying JSON attribute through a variable in preprocessing - PlantUML Q&A. In the given example, consider that I want to generate two separate diagrams, one based on "name" and the other based on "salary".

Since this "parametrization" seems not possible, I decided to use a "workaround" where I make the attributes into a list instead, like this:

!$foo = { "company": "Skynet", "employees" : [
  {"attributes": [
    {"key": "name", "value": "alice"},
    {"key": "salary", "value": 100 }
  ]},
  {"attributes": [
    {"key": "name", "value": "bob"},
    {"key": "salary", "value": 50}
  ]}
}

Then as a further "workaround", I will "understand" that index 0 is name, index 1 is salary. I will pass the index as a parameter to the procedure, which will "segregate" all the attributes at the index'th position. I call this procedure in a loop to generate multiple diagrams How to use newpage in wbs diagram? - PlantUML Q&A (but you have already commented with a "workaround" on that question, for which, thanks a lot! :-))

Here is a "real" demo example on my github: plantuml_demo/test_match_host_wbs_demo.puml at main · dragondive/plantuml_demo · GitHub [I made this example for self-learning, but now I want to reuse the code at work to make drawings for some idea proposals.]  

In this diagram, the "leaf nodes" have only one attribute "count", based on which the wbs is created. I want to now extend it so that the leaf nodes can have multiple attributes. From one json file, multiple diagrams will be generated, one for each such attribute. That's when I started looking for a way to "parametrize out" the hardcoded "count" attribute, and eventually drifted here with a series of failed workarounds. :-) 

commented Nov 6 by plantuml (262,900 points)
Maybe you can post here an small example with the syntax your are expecting.

We'll see if we could make it work :-)

Thanks,
commented Nov 6 by Martin (5,640 points)
edited Nov 7 by Martin

Your Github example looks very impressive. 
I agree that given the short-coming in Plantuml JSON that you can't build up the JSON name from other strings, that using a list of key/value pairs in an attributes list would be a clever workaround. (see next comment)


But before you reformat all your data, have you considered simply:

!if $thisrun="count"
  !$sum = $sum + %intval($item.count)
!endif
!if $thisrun="somethingelse"
  !$sum = $sum + %intval($item.somethingelse)
!endif

I know it is a little more hard-coding than you would like...

But say you do reformat your data so that each leaf node contains a list called 'attributes' of 'key' & 'value' pairs.  I don't understand why you can't just loop through using foreach, e.g.

!foreach $attr in $item.attributes
  !if $attr.key == $thisrun
    !$sum = $sum + %intval($attr.value)
  !endif
!endfor

Here it is


Then your final idea of having indexes $count=0, $somethingelse=1 and using $items.attributes[$count].value works fine too.
Here it is
 

commented Nov 7 by Martin (5,640 points)
edited Nov 7 by Martin

So as per The's answer to your other question, the short-coming was in my knowledge and Plantuml's documentation, not Plantuml itself.  It turns out you can avoid the above workarounds with:

!$attr = "count"
!$sum = $sum + %intval($item[$attr])

Here it is

commented Nov 13 by dragondive (440 points)
@Martin Wow, that's wonderful. :) I didn't know about the [] "operator". This should certainly simplify many things.
0 votes
answered Nov 9 by plantuml (262,900 points)

Probably useless, but we have just introduced a new %size() function :

commented Nov 10 by Martin (5,640 points)

Interesting.  So, from what I can make out it returns:

  • For a JSON Object:  the number of pairs it contains
  • For a JSON Array: the number of values it contains
  • For a string value: the number of characters it contains
  • For a numeric value: zero
  • For true/false/null: zero
PS
Given !$json={ "value" : [100, "100"] }
Is there a way to get a %size (in the same way) on each of the two elements such that the first element returns 0 and the second element returns 3?  Every method I've tried either returns 0 for both or 3 for both.
...