Add edges in diagram without touching existing JSON data

0 votes
asked Jan 22, 2022 in Question / help by Alex

Hey guys,

I have a couple of deeply nested JSON files which I successfully visualized in diagrams via PlantUML.

Inside my JSON data, there are some references which I want to visualize for readability reasons, similar to the much simplified example data below:

@startJSON
[{
    "fruits": [
        {
            "name": "apple",
            "colorId": "1"
        },
        {
            "name": "pear",
            "colorId": "2"
        },
        {
            "name": "pineapple",
            "colorId": "3"
        }
    ]
},
{
    "colors": [
        {
            "id": "1",
            "name": "red"
        },
        {
            "id": "2",
            "name": "green"
        },
        {
            "id": "3",
            "name": "yellow"
        }
    ]
}]
@endJSON

In the example above, I'd like to add edges between 'apple' and 'red' for example.

Is this possible using PlantUMLs preprocessing features?

If not, what should I do to achieve this?

Actually, PlantUML was the only tool I found which was able to visualize those nested JSON objects as trees.

1 Answer

0 votes
answered Jan 28, 2022 by Martin (8,360 points)
edited Jan 28, 2022 by Martin

It's not possible to refer to JSON nodes as if they are predefined objects.  Perhaps you could suggest a syntax for your "apple -> red" link.  e.g. Are you thinking something like: json::fruits::name("apple") --> json::colors::name("red") ?  Or were you hoping to automatically map all fruits colorid to their respective colors id?

Anyway, I had fun writing an alternative json visualiser in the object diagram syntax using the preprocessor.  It is likely not very robust as I've only tried it on the one example json - it's just to give you an idea of what's possible.

(click for online server)

@startuml
!$myjson = {
"root" : [{
    "fruits": [
        {
            "name": "apple",
            "colorId": "1"
        },
        {
            "name": "pear",
            "colorId": "2"
        },
        {
            "name": "pineapple",
            "colorId": "3"
        }
    ]
},
{
    "colors": [
        {
            "id": "1",
            "name": "red"
        },
        {
            "id": "2",
            "name": "green"
        },
        {
            "id": "3",
            "name": "yellow"
        }
    ]
}]
}

set namespaceSeparator none

!$i = 1

!procedure $debug($text1, $text2)
object %string($i + "_" + $text1) {
  %string($text2)
  %size($text2)
}
!$i = $i +1
!endprocedure

!procedure process_json($stem, $json)
!if %substr($json,0,1) != "{"
  map %string($stem) { 
  }
!endif  
!if %substr($json,0,1) == "["
  '!if $i > 1
  '  $debug("square", $json)
  '!endif
  !$j=0
  !foreach $node in $json
    '$debug("element", $node)
    process_json(%string($stem+"."+$j), $node)
    $stem --> %string($stem+"."+$j)
    !$j=$j+1
  !endfor
!elseif %substr($json,0,1) == "{"
      '$debug("curly", $json)
' parse for map
      map $stem {
      !$rest=$json
      !while $rest != "{"
        !$key = %substr($rest,2,%strpos($rest, ":")-3)
        !$value = $json[$key]
        !if %substr($value,0,1) != "["
          $key => $value
        !endif
        !$rest = "{" + %substr($rest,%strlen("{"+%chr(34)+$key+%chr(34)+":"+%chr(34)+$json[$key]+%chr(34)+"}"))
      !endwhile
      }
' parse again for recursions
      !$rest=$json
      !while $rest != "{"
        !$key = %substr($rest,2,%strpos($rest, ":")-3)
        !$value = $json[$key]
        !if %substr($value,0,1) == "["
          '$debug("key", $key)
          '$debug("value", $value)
          process_json(%string($stem+"."+$key), $value)      ​
         ​$stem --> %string($stem+"."+$key)
       ​!endif
       ​!$rest = "{" + %substr($rest,%strlen("{"+%chr(34)+$key+%chr(34)+":"+%chr(34)+$value+%chr(34)+"}"))
       ​'$debug("rest", $rest)
     ​!endwhile
!else
 ​'$debug("string", $json)
!endif

!endprocedure

process_json("root", $myjson.root)

root.0.fruits.0::apple -> root.1.colors.0::red

@enduml

commented Jan 28, 2022 by Alex
Wow, thanks for your response and the JSON visualizer code!

Actually automatic mapping would be perfect but I'll look into an alternative solution, maybe I can generate the code for the edges in a language I'm more safe with.
commented Jan 30, 2022 by Martin (8,360 points)
edited Jan 31, 2022 by Martin

Thanks to @Plantuml for introducing two new buitlins, I can simplify the parser slightly:

click for online server

@startuml
!$myjson = {
"root" : [{
    "fruits": [
        {
            "name": "apple",
            "colorId": "1"
        },
        {
            "name": "pear",
            "colorId": "2"
        },
        {
            "name": "pineapple",
            "colorId": "3"
        }
    ]
},
{
    "colors": [
        {
            "id": "1",
            "name": "red"
        },
        {
            "id": "2",
            "name": "green"
        },
        {
            "id": "3",
            "name": "yellow"
        }
    ]
}]
}

!procedure process_json($stem, $json)
  !if %get_json_type($json) == "array"
    map %string($stem) { 
    }
    !$j=0
    !foreach $node in $json
      process_json(%string($stem+"."+$j), $node)
      $stem --> %string($stem+"."+$j)
      !$j=$j+1
    !endfor
  !elseif %get_json_type($json) == "object"
    ' parse for map
    map $stem {
    !foreach $key in %get_json_keys($json)
      !$value = $json[$key]
      !if %get_json_type($value) != "array"
        $key => $value
      !endif
    !endfor
    }
    ' parse again for recursions into sub-arrays
    !foreach $key in %get_json_keys($json)
      !$key2 = $key           /' foreach uses a global $key, so we need to move to a local $key2 so we can recurse '/
      !$value = $json[$key2]  /' %get_json_type only takes standard variables, so move to $value '/
      !if %get_json_type($value) == "array"
        process_json(%string($stem+"."+$key2), $value)      
        $stem ---> %string($stem+"."+$key2)
      !endif
    !endfor
  !else 
    ' nothing to do for string/number/boolean/null
  !endif

!endprocedure

skinparam labelFontSize 7
label A [
process_json("root", $myjson.root)
root.0.fruits.0::apple -> root.1.colors.0::red
]

label B [
{{
set namespaceSeparator none
process_json("root", $myjson.root)
root.0.fruits.0::apple -> root.1.colors.0::red
}}
]
@enduml
...