jython-2.7

How to transform dicts/lists into Tree component structure?


I'm trying to convert dicts and lists into a structure for a Tree view component (Ignition Perspective component, if it's relevant) which requires it in a specific format, but I'm starting to feel stupid as I just can't get it right.

I need to convert this:

{
    'barrelIds': ['BOB', 'SARAH'],
    'executionMode': 'EMPTY_FILL',
    'emptyRequest': {
        'barrelGroupId': 123458,
        'requestId': 7832
    },
    'complex': {
        'sub-complex': ['BOB', 'SARAH'],
        'sub-complex2': {
            'barrelGroupId': 123458,
            'barrels': [{
                    'item': 1
                }, {
                    'item': 2
                }
            ]
        }
    }
}

into this:

[
  {
    "label": "barrelIds",
    "expanded": True,
    "items": [
      {
        "label": "BOB",
        "expanded": True,
        "items": []
      },
      {
        "label": "SARAH",
        "expanded": True,
        "items": []
      }
    ]
  },
  {
    "label": "executionMode = 'EMPTY_FILL'",
    "expanded": True,
    "items": []
  },
  {
    "label": "emptyRequest",
    "expanded": True,
    "items": [
      {
        "label": "barrelGroupId = 123458",
        "expanded": True,
        "items": []
      },
      {
        "label": "requestId = 7832",
        "expanded": True,
        "items": []
      }
    ]
  },
  {
    "label": "complex",
    "expanded": True,
    "items": [
      {
        "label": "sub-complex",
        "expanded": True,
        "items": [
          {
            "label": "BOB",
            "expanded": False,
            "items": []
          },
          {
            "label": "SARAH",
            "expanded": False,
            "items": []
          }
        ]
      },
      {
        "label": "sub-complex2",
        "expanded": True,
        "items": [
          {
            "label": "barrelGroupId = 12358",
            "expanded": True,
            "items": []
          },
          {
            "label": "barrels",
            "expanded": True,
            "items": [
              {
                "label": "[0]",
                "expanded": True,
                "items": [
                  {
                    "label": "item = 1",
                    "expanded": False,
                    "items": []
                  },
                  {
                    "label": "other = 1",
                    "expanded": False,
                    "items": []
                  }
                ]
              },
              {
                "label": "[1]",
                "expanded": True,
                "items": [
                  {
                    "label": "item = 2",
                    "expanded": True,
                    "items": []
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

What this looks like in the Tree is this:

enter image description here

Essentially, the basic rules are:

I will include code, but I'm sure it should be rewritten in its entirety...

def get_tree_items(data):
    # TODO:
    # 1. simplify this. top conditions shouldn't have sub-fors.
    # 2. empty dicts are not handled.

    if isinstance(data, dict):
        items = []
        for key, value in data.items():
            if isinstance(value, list):
                sub_items = get_tree_items(value)
                items.append(format_value(key, value, sub_items))
                
            elif isinstance(value, dict):
                sub_items = get_tree_items(value)
                items.append(format_value(key, value, sub_items))
                
            else:
                items.append({'label': '{} = {}'.format(key, repr(value)), 'items': [],
                              'expanded': True}) 
        return items
    elif isinstance(data, list):
        items = []
        for idx, item in enumerate(data):
            new_item = get_tree_items(item)
            items.append(new_item)
        return items
    else:
        return {'label': data, 'items': [], 'expanded': True}

def format_value(key, value, sub_items):
    if isinstance(sub_items, list):
        return {'label': key, 'items': sub_items, 'expanded': True}
    else:
        return {'label': '{} = {}'.format(key, value), 'items': [], 'expanded': True}

This currently produces this output:

[{
        'label': 'barrelIds',
        'items': [{
                'label': 'BOB',
                'items': [],
                'expanded': True
            }, {
                'label': 'SARAH',
                'items': [],
                'expanded': True
            }
        ],
        'expanded': True
    }, {
        'label': "executionMode = 'EMPTY_FILL'",
        'items': [],
        'expanded': True
    }, {
        'label': 'emptyRequest',
        'items': [{
                'label': 'barrelGroupId = 123458',
                'items': [],
                'expanded': True
            }, {
                'label': 'requestId = 7832',
                'items': [],
                'expanded': True
            }
        ],
        'expanded': True
    }, {
        'label': 'complex',
        'items': [{
                'label': 'sub-complex',
                'items': [{
                        'label': 'BOB',
                        'items': [],
                        'expanded': True
                    }, {
                        'label': 'SARAH',
                        'items': [],
                        'expanded': True
                    }
                ],
                'expanded': True
            }, {
                'label': 'sub-complex2',
                'items': [{
                        'label': 'barrelGroupId = 123458',
                        'items': [],
                        'expanded': True
                    }, {
                        'label': 'barrels',
                        'items': [[{
                                    'label': 'item = 1',
                                    'items': [],
                                    'expanded': True
                                }
                            ], [{
                                    'label': 'item = 2',
                                    'items': [],
                                    'expanded': True
                                }
                            ]],
                        'expanded': True
                    }
                ],
                'expanded': True
            }
        ],
        'expanded': True
    }
]

and this Tree view (note missing sub-complex2.barrels tree branches - everything else is correct):

enter image description here


Solution

  • I got it, finally! I rewrote the function from the ground up.

    def process(obj, key_=None, idx_=None):
        this_fn = process
    
        if isinstance(obj, dict):
            items = []
            for key, value in obj.items():
                items.append(this_fn(value, key_=key))
    
            item = None
            if key_ is not None:
                item = {'label': key_, 'items': items, 'expanded': True}
            elif idx_ is not None:
                item = {'label': '[{}]'.format(idx_), 'items': items, 'expanded': True}
            else:
                item = items
            return item
    
        if isinstance(obj, list):
            items = []
            for idx, value in enumerate(obj):
                items.append(this_fn(value, idx_=idx))
    
            item = None
            if key_ is not None:
                item = {'label': key_, 'items': items, 'expanded': True}
            elif idx_ is not None:
                item = {'label': '[{}]'.format(idx_), 'items': items, 'expanded': True}
            else: # is this even possible to get to??
                raise ValueError('How did you get here??')
                item = {'label': '???', 'items': items, 'expanded': True}
            return item
    
        else: # basic type, int, long, str, etc.
            if idx_ is not None:
                item = {'label': '[{}] = {}'.format(idx_, repr(obj)), 'items': [], 'expanded': True}
            elif key_ is not None:
                item = {'label': '{} = {}'.format(key_, repr(obj)), 'items': [], 'expanded': True}
            else: # this should NEVER be an option...
                raise ValueError('What?')
            return item