> For the complete documentation index, see [llms.txt](https://timhwang21.gitbook.io/index/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://timhwang21.gitbook.io/index/programming/typescript/safe-json-clone.md).

# Safe JSON clone

Calling `JSON.serialize` and then `JSON.parse` on a value is the fastest general-purpose way to deeply clone an object in Javascript. However, the downside is that it only works on values for which `x => JSON.parse(JSON.serialize(x))` is the identity function. In other words, this function will only work correctly on strings, numbers, booleans, `null`, `undefined`, and arrays and objects built off of these types.

It's common to choose a function name or leave comments warning about this constraint. However, in Typescript we can enforce this constraint at the type level using a recursive type:

```typescript
// note that the primitives Symbol and BigInt are not serializable
// this is also the same as the JSON type from Typescript's docs, but with the
// addition of undefined as a valid value:
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#more-recursive-type-aliases
type FastClonable =
  | string
  | number
  | boolean
  | null
  | undefined
  | FastClonable[]
  | { [x: string]: FastClonable }

const fastClone = <T extends FastClonable>(x: T): T = JSON.parse(JSON.serialize(x))
```

Now, when passing any value that will not be transparently cloned via this function, Typescript will emit an error.

```typescript
// valid
fastClone(123)
fastClone([123, true, 'foo'])
fastClone({ foo: 123 })

// invalid
fastClone(Symbol('foo'))
fastClone(123n)
fastClone(new Foo())
```

This also works generically. However, note that this approach does not work when passed an interface, [which is due to an intentional Typescript design decision](https://github.com/microsoft/TypeScript/issues/15300#issuecomment-332366024). (Note that this may change in future versions of Typescript.)

```typescript
type FooType = {
  bar: {
    baz: number
  }
}

interface FooInterface {
  bar: {
    baz: number
  }
}

const myFoo = {
  bar: {
    baz: 123
  }
}

// correctly returns a value with type FooType
fastClone(myFoo as FooType)

// Argument of type 'FooInterface' is not assignable to parameter of type 'FastClonable'.
//   Type 'FooInterface' is not assignable to type '{ [x: string]: FastClonable; }'.
//     Index signature is missing in type 'FooInterface'.
fastClone(myFoo as FooInterface)
```
