zed icon indicating copy to clipboard operation
zed copied to clipboard

Add "into" operator (like "over" but for recursive descent)

Open philrz opened this issue 3 years ago • 1 comments

The derived values produced by the over operator are top-level only. The language would benefit from a way to achieve something similar while descending recursively into objects. @mccanne has noted that with_entries from jq might be a good place to look for ideas.

Note that the language does seem to have an approach to this that may work in the meantime, albeit by piecing together several language elements. As shown with a specific example in #4050, the essence is to use flatten() before operating on each key/value pair, then collect() up the flattened key/value pairs, then unflatten() to restore the original nested structure.

philrz avatar Aug 30 '22 01:08 philrz

Here's an example from a community user that was recently floated in a Slack thread.

The area where they expressed interest was "doing deep mutations on a JSON document", saying mostly what they want to do is:

  • search through every level of the JSON document
  • match objects
  • do stuff to them

Some more specifics:

  • foreach object matching a certain shape anywhere in the JSON document
    • update value(s)
    • remove value(s)
  • at a specific path
    • update value(s)
    • remove value(s)
  • probably for arrays, support things like
    • push
    • remove value

They showed a very specific example using this file test.json, which is an excerpt from an OpenAPI spec.

{
  "openapi": "3.0.0",
  "info": {
    "title": "Demo API",
    "description": "The Demo API is not a real API.",
    "version": "1.0.0"
  },
  "paths": {
    "/users": {
      "post": {
        "summary": "Create user",
        "description": "Creates a user",
        "operationId": "postUsers",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The user's first + last name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The created user's ID"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "country": {
        "type": "string",
        "nullable": true,
        "enum": [
          "foo",
          "bar"
        ]
      }
    }
  }
}

The challenge presented is to locate any enum schema object marked nullable: true and add null as an enum value if it doesn't already exist at that level. They offered the following jq command line which gets the job done.

$ cat test.json | jq '(
  recurse | 
  select(type == "object" and .nullable == true and has("enum")) | 
  .enum
) |= (.+ [null] | unique)'
...
  "components": {
    "schemas": {
      "country": {
        "type": "string",
        "nullable": true,
        "enum": [
          null,
          "bar",
          "foo"
        ]
      }
    }
  }
}

Looking at what's present in Zed currently, I don't think I see a way to achieve this. For instance, the example in #4050 showed how we could use the flatten(), collect() and unflatten() combination to manipulate a single value that's found by recursively walking through a deeply nested object, but I don't see a way to simultaneously target "peer" values, e.g., checking for the presence of the enum array and then extending it.

philrz avatar Nov 14 '22 20:11 philrz