parsed_it

Package Version Hex Docs

A parsing and serialization library for JSON and XML in Gleam, supporting both Erlang and JavaScript targets.

Installation

gleam add parsed_it@0.1

Quick Start

JSON Parsing

import gleam/dynamic/decode
import parsed_it/json

type User {
  User(name: String, email: String)
}

fn user_decoder() -> decode.Decoder(User) {
  use name <- decode.field("name", decode.string)
  use email <- decode.field("email", decode.string)
  decode.success(User(name:, email:))
}

pub fn example() {
  let json_string = "{\"name\":\"Lucy\",\"email\":\"lucy@example.com\"}"
  let result = json.parse(from: json_string, using: user_decoder())
  // result == Ok(User("Lucy", "lucy@example.com"))
}

XML Parsing

import gleam/dynamic/decode
import parsed_it/xml

type Book {
  Book(title: String, author: String)
}

fn book_decoder() -> decode.Decoder(Book) {
  use title <- decode.field("title", decode.field("$text", decode.string))
  use author <- decode.field("author", decode.field("$text", decode.string))
  decode.success(Book(title:, author:))
}

pub fn example() {
  let xml_string = "<book><title>Gleam Guide</title><author>Lucy</author></book>"
  let result = xml.parse(from: xml_string, using: book_decoder())
  // result == Ok(Book(title: "Gleam Guide", author: "Lucy"))
}

Building JSON/XML

import parsed_it/json
import parsed_it/xml

pub fn json_example() {
  json.object([
    #("name", json.string("Lucy")),
    #("age", json.int(30)),
  ])
  |> json.to_string
  // "{\"name\":\"Lucy\",\"age\":30}"
}

pub fn xml_example() {
  xml.element("user", [xml.attr("id", "1")], [
    xml.element("name", [], [xml.string("Lucy")]),
  ])
  |> xml.to_string
  // "<user id=\"1\"><name>Lucy</name></user>"
}

Module Organization

This library uses parsed_it/* as the module namespace:

Import modules like this:

import parsed_it/json
import parsed_it/xml

API Design

Type-Safe Parsing with parse

The primary API uses labeled arguments for clarity:

json.parse(from: json_string, using: decoder)
xml.parse(from: xml_string, using: decoder)

Dynamic Parsing with parse_dynamic

For cases where you need to inspect raw structure before decoding:

json.parse_dynamic(from: json_string)
xml.parse_dynamic(from: xml_string)

XML Dynamic Structure

When using parse_dynamic or writing decoders for XML, understand the structure that the parser produces:

// XML: <book id="123"><title>Hello</title></book>
// Becomes:
// {
//   "$tag": "book",
//   "$attrs": { "id": "123" },
//   "title": { "$tag": "title", "$text": "Hello" }
// }

Special keys:

Child Element Multiplicity

Important: Child elements with the same tag name are grouped into arrays, while unique children remain as single objects:

// XML: <list><item>A</item><item>B</item></list>
// Becomes: { "$tag": "list", "item": [{ "$tag": "item", "$text": "A" }, ...] }

// XML: <list><item>A</item></list>
// Becomes: { "$tag": "list", "item": { "$tag": "item", "$text": "A" } }  // NOT an array!

Write decoders that handle both cases if the multiplicity can vary:

fn items_decoder() -> decode.Decoder(List(String)) {
  decode.one_of([
    // Handle array case
    decode.field("item", decode.list(decode.field("$text", decode.string))),
    // Handle single element case
    decode.field("item", decode.field("$text", decode.string))
    |> decode.map(fn(s) { [s] }),
  ])
}

Leaf Elements and Tag Names

When a leaf element (no children, only text) is accessed as a child, you get the full element object including $tag. The text is in $text:

// To decode: <name>Lucy</name>
decode.field("name", decode.field("$text", decode.string))

Known Issues

IssuePlatformImpact
Unicode corruption in XMLErlangMulti-byte UTF-8 may be corrupted
Error message extractionJavaScriptMay return empty error details in some engines

Error Handling

JSON Errors

pub type JsonDecodeError {
  UnexpectedEnd          // JSON truncated
  UnexpectedChar(String) // Invalid character (hex code)
  UnexpectedSequence(String)
  UnableToDecode(List(DecodeError)) // Valid JSON, wrong shape
}

XML Errors

pub type XmlDecodeError {
  InvalidXml(String)     // Malformed XML
  UnableToDecode(List(DecodeError)) // Valid XML, wrong shape
}

Note: Error types are currently public ADTs. This means adding new error variants in future versions would be a breaking change. We may make these opaque in a future major version to allow for better error handling evolution.

Development

gleam test  # Run the tests
gleam docs build  # Build documentation

Further Documentation

API documentation is available at https://hexdocs.pm/parsed_it.

Search Document