Number of elements in list during json preprocessing

0 votes
asked Nov 6, 2021 in Wanted features by dragondive (1,360 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, 2021 by plantuml (295,760 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, 2021 by dragondive (1,360 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, 2021 by Martin (9,120 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, 2021 by dragondive (1,360 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, 2021 by plantuml (295,760 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, 2021 by Martin (9,120 points)
edited Nov 7, 2021 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, 2021 by Martin (9,120 points)
edited Nov 7, 2021 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, 2021 by dragondive (1,360 points)
@Martin Wow, that's wonderful. :) I didn't know about the [] "operator". This should certainly simplify many things.
0 votes
answered Nov 9, 2021 by plantuml (295,760 points)

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

commented Nov 10, 2021 by Martin (9,120 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.
...