expand = input => {
  if (input === undefined || input === null) {
    return input
  }

  const keys = Object.keys(input)

  const result = {}
  let resultPtr = result

  for (let path of keys) {
    const originalPath = path
    if (path.indexOf('[') >= 0) {
      path = path.replace(/\[/g, '.').replace(/]/g, '')
    }

    const parts = path.split('.')

    const check = parts.filter(blocklistFilter)

    if (check.length !== parts.length) {
      return undefined
    }
    resultPtr = result
    const lastIndex = parts.length - 1

    for (let i = 0; i < parts.length; ++i) {
      const part = parts[i]
      if (i === lastIndex) {
        if (Array.isArray(resultPtr)) {
          resultPtr[+part] = input[originalPath]
        } else {
          resultPtr[part] = input[originalPath]
        }
      } else {
        if (resultPtr[part] === undefined || resultPtr[part] === null) {
          const nextPart = parts[i + 1]
          if (/^\d+$/.test(nextPart)) {
            resultPtr[part] = []
          } else {
            resultPtr[part] = {}
          }
        }
        resultPtr = resultPtr[part]
      }
    }
  }
  return result
}
Converts flat objects whose keys use JSON path notation to nested objects.

The input object is not mutated.

@example
{ 'foo[bar][0]': 'baz' } -> { foo: { bar: [ 'baz' ] } }