squirtle

squirtle - RFC 6902 JSON Patch for Gleam

Apply patches to JSON documents, generate diffs, and serialize patches.

Types

A JSON document that can be patched.

This is a concrete representation of JSON that supports pattern matching and traversal, unlike gleam/json’s opaque types.

pub type Doc {
  Null
  String(String)
  Int(Int)
  Bool(Bool)
  Float(Float)
  Array(List(Doc))
  Object(dict.Dict(String, Doc))
}

Constructors

  • Null
  • String(String)
  • Int(Int)
  • Bool(Bool)
  • Float(Float)
  • Array(List(Doc))
  • Object(dict.Dict(String, Doc))

An RFC 6902 JSON Patch operation.

Patches describe transformations to apply to a JSON document. When applied in sequence, they transform the document step by step.

pub type Patch {
  Add(path: String, value: Doc)
  Remove(path: String)
  Replace(path: String, value: Doc)
  Copy(from: String, to: String)
  Move(from: String, to: String)
  Test(path: String, expect: Doc)
}

Constructors

  • Add(path: String, value: Doc)

    Add a value at the target path. If the path points to an array index, the value is inserted at that position.

  • Remove(path: String)

    Remove the value at the target path.

  • Replace(path: String, value: Doc)

    Replace the value at the target path with a new value. The path must already exist.

  • Copy(from: String, to: String)

    Copy the value from one path to another.

  • Move(from: String, to: String)

    Move the value from one path to another (copy then remove).

  • Test(path: String, expect: Doc)

    Test that the value at a path equals the expected value. If the test fails, the entire patch operation fails.

Errors that can occur when applying patches.

pub type PatchError {
  PathNotFound(path: String)
  InvalidIndex(path: String, index: String)
  IndexOutOfBounds(path: String, index: Int)
  NotAContainer(path: String)
  CannotRemoveRoot
  TestFailed(path: String, expected: Doc, actual: Doc)
  InvalidPath(reason: String)
}

Constructors

  • PathNotFound(path: String)

    The specified path does not exist in the document.

  • InvalidIndex(path: String, index: String)

    An array index in the path is invalid (not a number, has leading zeros, etc).

  • IndexOutOfBounds(path: String, index: Int)

    An array index is outside the bounds of the array.

  • NotAContainer(path: String)

    Attempted to navigate into a value that is not an object or array.

  • CannotRemoveRoot

    Cannot remove the root document.

  • TestFailed(path: String, expected: Doc, actual: Doc)

    A test operation failed because the values didn’t match.

  • InvalidPath(reason: String)

    The JSON pointer path is malformed.

Values

pub fn apply(
  doc: Doc,
  patches: List(Patch),
) -> Result(Doc, PatchError)

Apply a list of patches to a document.

Patches are applied in order. If any patch fails, the operation stops and returns an error. All patches must succeed for the operation to succeed.

Example

let assert Ok(doc) = squirtle.parse("{\"name\": \"John\"}")
let patches = [
  squirtle.Replace(path: "/name", value: squirtle.String("Jane")),
  squirtle.Add(path: "/age", value: squirtle.Int(30)),
]
squirtle.apply(doc, patches)
// => Ok(Object(...))
pub fn decode(
  doc: Doc,
  with decoder: decode.Decoder(a),
) -> Result(a, List(decode.DecodeError))

Decode a Doc into a custom type using a decoder.

Example

let doc = squirtle.Object(dict.from_list([#("name", squirtle.String("John"))]))
squirtle.decode(doc, decode.field("name", decode.string))
// => Ok("John")
pub fn decoder() -> decode.Decoder(Doc)

Decoder for parsing JSON into a Doc.

Use this with gleam/json.parse for custom parsing needs.

pub fn diff(from from: Doc, to to: Doc) -> List(Patch)

Generate a list of patches that transform one document into another.

The returned patches, when applied to from, will produce to.

Example

let from = squirtle.Object(dict.from_list([
  #("name", squirtle.String("John")),
]))
let to = squirtle.Object(dict.from_list([
  #("name", squirtle.String("Jane")),
  #("age", squirtle.Int(30)),
]))

squirtle.diff(from, to)
// => [Replace("/name", String("Jane")), Add("/age", Int(30))]
pub fn error_to_string(error: PatchError) -> String

Convert a PatchError to a human-readable string.

pub fn parse(
  json_string: String,
) -> Result(Doc, json.DecodeError)

Parse a JSON string into a Doc.

Example

squirtle.parse("{\"name\": \"John\", \"age\": 30}")
// => Ok(Object(...))
pub fn parse_patches(
  json_string: String,
) -> Result(List(Patch), json.DecodeError)

Parse a JSON array of patch operations from a string.

Example

squirtle.parse_patches("[{\"op\": \"add\", \"path\": \"/name\", \"value\": \"John\"}]")
// => Ok([Add("/name", String("John"))])
pub fn patch_decoder() -> decode.Decoder(Patch)

Decoder for parsing a single patch operation.

pub fn patch_to_doc(patch: Patch) -> Doc

Convert a patch to its Doc representation (for custom serialization).

pub fn patch_to_string(patch: Patch) -> String

Convert a single patch to a JSON string.

pub fn patches_to_string(patches: List(Patch)) -> String

Convert a list of patches to a JSON array string.

Example

let patches = [
  squirtle.Add(path: "/name", value: squirtle.String("John")),
]
squirtle.patches_to_string(patches)
// => "[{\"op\":\"add\",\"path\":\"/name\",\"value\":\"John\"}]"
pub fn to_dynamic(doc: Doc) -> dynamic.Dynamic

Convert a Doc to a Dynamic value.

Useful when you need to decode a Doc into a custom Gleam type using gleam/dynamic/decode.

pub fn to_json(doc: Doc) -> json.Json

Convert a Doc to gleam/json’s Json type.

Useful when you need to integrate with code that uses the standard library.

pub fn to_string(doc: Doc) -> String

Convert a Doc to a JSON string.

Example

let doc = squirtle.Object(dict.from_list([#("name", squirtle.String("John"))]))
squirtle.to_string(doc)
// => "{\"name\":\"John\"}"
Search Document