2

I use Esprima to generate AST of a modern JSX component. It works great. I serilize that AST to a JSON in a file and load it in a Python file and I want to traverse each node in it.

I tried the esprima-ast-visitor but I get this error:

visitor.UnknownNodeTypeError: ImportDeclaration

I guess it's because that's an old library. I have also seen some examples on how to visit nodes in a JSON, yet they are all about specific examples on how to extract such value at such depth, or they are about searching.

But I want to be able to visit every node of a given JSON, and at the same time have access to its direct parent and children. How can I do that?

Basically I need a method with this signature:

import json

jsonString=open('json-path').read()
jsonRoot=json.loads(jsonString)

# this is a pseudo code, invalid in Python syntax, written in JS style
jsonRoot.visit(({ node, parent, children }) => {
    # do something with the node here
})

P.S. I'm new to Python. That's why I can't write it.

Big boy
  • 1,113
  • 2
  • 8
  • 23
  • 1
    So if you arrive at a list, like `[1, 2, 3]` then `node` should be that list, and `children` should be a list of the inner values, i.e. the *same* list? I don't see the usefulness of repeating that. Can you provide a simple, small example of JSON with a combination of objects and arrays and list how many times the `visit` callback should be called, and what it arguments should be? – trincot Jun 21 '23 at 06:49

1 Answers1

3

I need a method with this signature [...] jsonRoot.visit(({ node, parent, children }) =>

It does not seem useful to pass children to the callback, since you already have node. Imagine that node is a list like [1, 2, 3], then children would be a list of the child values, which ... is again [1, 2, 3]. Admittedly, if node is a dict (derived from an object notation in JSON), then its children would be list and not a dict, but it is a piece of cake to get the children with node.values() (In JavaScript: Object.values(node))...

I would suggest you omit children, but instead provide the key or index where the node resides in the parent: that is more useful information. You can replace that callback mechanism with a generator (also in JavaScript). In Python that would be:

def iterate_hierarchy(node, parent=None, key=None):
    yield parent, key, node
    if isinstance(node, dict):
        for key, child in node.items():
            yield from iterate_hierarchy(child, node, key)
    elif isinstance(node, list):
        for key, child in enumerate(node):
            yield from iterate_hierarchy(child, node, key)

Here is an example run:

root = [{
    "nums": [1, 2, 3],
    "objs": [{ "a": "aa", "b": "bb"}, {"c": "cc"}]
}, {
    "nums": [4, 5, 6],
    "objs": [{ "A": "AA", "B": "BB"}, {"C": "CC"}]
}]

for parent, key, node in iterate_hierarchy(root):
    print(parent, key, node)
trincot
  • 317,000
  • 35
  • 244
  • 286
  • I tried your code with [this JSON](https://jsonbin.io/quick-store/6492d6d38e4aa6225eb1f124) and I tried `if hasattr(node, 'type') and node.type == 'JSXText':` instead of `print`, and it did not work. It did not give me `JSXText` nodes. – Big boy Jun 21 '23 at 11:19
  • That is a followup question and is unrelated to the iteration over the nested object structure, but with the creation of JSXText nodes. The code you shared in the comment does not attempt to create such nodes, but this is not in the scope of your original question. Please consider asking a new question if you have a problem in creating nodes. – trincot Jun 21 '23 at 11:48
  • is it wrong to expect the `{ type: 'JSXText', value: 'Download' }` to be a node and accessible in `node` object? – Big boy Jun 21 '23 at 11:50
  • Please, this comments section is not intended to discuss follow-up questions. Your question was about iteration over the nested object, not about creating JSXText objects. If you cannot make it work, please ask a new question. – trincot Jun 21 '23 at 11:52
  • thanks for guiding me. I'll ask a new question. Thank you. – Big boy Jun 21 '23 at 11:53
  • Just one additional note: when you parse JSON (like with `json.loads`), you get values of the type `dict`, `list`, `str`, `int`, `float`, `bool`, or `NoneType` type, and a nested mix of those. JSON does not know about other types, and if you need something else, you'll need to *construct* it. – trincot Jun 21 '23 at 11:59