Hyperlinkv0.8.0-beta.28

SchemaGetter

SchemaGetter.makeTreeRecordfunctioneffect/SchemaGetter.ts:1764
<A>(
  bracketPathEntries: ReadonlyArray<
    readonly [bracketPath: string, value: A]
  >
): Schema.TreeRecord<A>

Builds a nested tree object from a list of bracket-path entries.

When to use

Use when you need a schema getter to parse FormData or URLSearchParams entries into structured objects.

  • You have flat key-value pairs with bracket-path keys that need nesting.

Details

  • A bracket path is a string like "user[address][city]" that describes nested object/array structure.
  • Interprets bracket paths and constructs the corresponding nested object.
  • Builds and returns a nested object from the input entries.
  • Supported syntax:
    • "foo" → object key "foo"
    • "foo[bar]" → nested { foo: { bar: ... } }
    • "foo[0]" → array index { foo: [value] }
    • "foo[]" → append to array foo
    • "" → real empty key
  • Duplicate keys for the same path are merged into arrays.

Example (Building a tree from bracket paths)

import { SchemaGetter } from "effect"

const tree = SchemaGetter.makeTreeRecord([
  ["user[name]", "Alice"],
  ["user[tags][]", "admin"],
  ["user[tags][]", "editor"]
])
// { user: { name: "Alice", tags: ["admin", "editor"] } }
export function makeTreeRecord<A>(
  bracketPathEntries: ReadonlyArray<readonly [bracketPath: string, value: A]>
): Schema.TreeRecord<A> {
  const out: any = {}
  bracketPathEntries.forEach(([key, value]) => {
    const tokens = bracketPathToTokens(key)
    let cur: any = out
    tokens.forEach((token, i) => {
      const isLast = i === tokens.length - 1

      // We are inside an array and see "[]" (empty token) => append
      if (Array.isArray(cur) && token === "") {
        if (isLast) {
          cur.push(value)
        } else {
          // bracket path: "foo[][bar]" => push a new element and descend into it
          const next = tokens[i + 1]
          const shouldBeArray = typeof next === "number" || next === ""
          const index = cur.length
          cur = getOrCreateContainer(cur, index, shouldBeArray)
        }
      } else if (isLast) {
        // If we're setting a value at a path that already exists
        // convert it to an array to support multiple values for the same key
        const hasOwn = Object.hasOwn(cur, token)
        if (hasOwn && Array.isArray(cur[token])) {
          cur[token].push(value)
        } else if (hasOwn) {
          internalRecord.set(cur, token, [cur[token], value])
        } else {
          internalRecord.set(cur, token, value)
        }
      } else {
        const next = tokens[i + 1]
        // if next is a number OR "" (from []), we are building an array
        const shouldBeArray = typeof next === "number" || next === ""
        cur = getOrCreateContainer(cur, token, shouldBeArray)
      }
    })
  })
  return out
}
Referenced by 2 symbols