parsed_it

A parsing and serialization library for JSON and XML in Gleam, supporting both
Erlang and JavaScript targets.
Installation
Quick Start
JSON Parsing
gleam
| 1 | import gleam/dynamic/decode |
| 2 | import parsed_it/json |
| 3 | |
| 4 | type User { |
| 5 | User(name: String, email: String) |
| 6 | } |
| 7 | |
| 8 | fn user_decoder() -> decode.Decoder(User) { |
| 9 | use name <- decode.field("name", decode.string) |
| 10 | use email <- decode.field("email", decode.string) |
| 11 | decode.success(User(name:, email:)) |
| 12 | } |
| 13 | |
| 14 | pub fn example() { |
| 15 | let json_string = "{\"name\":\"Lucy\",\"email\":\"lucy@example.com\"}" |
| 16 | let result = json.parse(from: json_string, using: user_decoder()) |
| 17 | // result == Ok(User("Lucy", "lucy@example.com")) |
| 18 | } |
XML Parsing
gleam
| 1 | import gleam/dynamic/decode |
| 2 | import parsed_it/xml |
| 3 | |
| 4 | type Book { |
| 5 | Book(title: String, author: String) |
| 6 | } |
| 7 | |
| 8 | fn book_decoder() -> decode.Decoder(Book) { |
| 9 | use title <- decode.field("title", decode.field("$text", decode.string)) |
| 10 | use author <- decode.field("author", decode.field("$text", decode.string)) |
| 11 | decode.success(Book(title:, author:)) |
| 12 | } |
| 13 | |
| 14 | pub fn example() { |
| 15 | let xml_string = "<book><title>Gleam Guide</title><author>Lucy</author></book>" |
| 16 | let result = xml.parse(from: xml_string, using: book_decoder()) |
| 17 | // result == Ok(Book(title: "Gleam Guide", author: "Lucy")) |
| 18 | } |
Building JSON/XML
gleam
| 1 | import parsed_it/json |
| 2 | import parsed_it/xml |
| 3 | |
| 4 | pub fn json_example() { |
| 5 | json.object([ |
| 6 | #("name", json.string("Lucy")), |
| 7 | #("age", json.int(30)), |
| 8 | ]) |
| 9 | |> json.to_string |
| 10 | // "{\"name\":\"Lucy\",\"age\":30}" |
| 11 | } |
| 12 | |
| 13 | pub fn xml_example() { |
| 14 | xml.element("user", [xml.attr("id", "1")], [ |
| 15 | xml.element("name", [], [xml.string("Lucy")]), |
| 16 | ]) |
| 17 | |> xml.to_string |
| 18 | // "<user id=\"1\"><name>Lucy</name></user>" |
| 19 | } |
Module Organization
This library uses parsed_it/* as the module namespace:
parsed_it/json - JSON parsing and serializationparsed_it/xml - XML parsing and serialization
Import modules like this:
gleam
| 1 | import parsed_it/json |
| 2 | import parsed_it/xml |
API Design
Type-Safe Parsing with parse
The primary API uses labeled arguments for clarity:
gleam
| 1 | json.parse(from: json_string, using: decoder) |
| 2 | xml.parse(from: xml_string, using: decoder) |
Dynamic Parsing with parse_dynamic
For cases where you need to inspect raw structure before decoding:
gleam
| 1 | json.parse_dynamic(from: json_string) |
| 2 | 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:
gleam
| 1 | // XML: <book id="123"><title>Hello</title></book> |
| 2 | // Becomes: |
| 3 | // { |
| 4 | // "$tag": "book", |
| 5 | // "$attrs": { "id": "123" }, |
| 6 | // "title": { "$tag": "title", "$text": "Hello" } |
| 7 | // } |
Special keys:
$tag - The element's tag name (always present)$attrs - Object containing attributes (present if element has attributes)$text - Text content (present if element has text content)
Child Element Multiplicity
Important: Child elements with the same tag name are grouped into arrays,
while unique children remain as single objects:
gleam
| 1 | // XML: <list><item>A</item><item>B</item></list> |
| 2 | // Becomes: { "$tag": "list", "item": [{ "$tag": "item", "$text": "A" }, ...] } |
| 3 | |
| 4 | // XML: <list><item>A</item></list> |
| 5 | // Becomes: { "$tag": "list", "item": { "$tag": "item", "$text": "A" } } // NOT an array! |
Write decoders that handle both cases if the multiplicity can vary:
gleam
| 1 | fn items_decoder() -> decode.Decoder(List(String)) { |
| 2 | decode.one_of([ |
| 3 | // Handle array case |
| 4 | decode.field("item", decode.list(decode.field("$text", decode.string))), |
| 5 | // Handle single element case |
| 6 | decode.field("item", decode.field("$text", decode.string)) |
| 7 | |> decode.map(fn(s) { [s] }), |
| 8 | ]) |
| 9 | } |
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:
gleam
| 1 | // To decode: <name>Lucy</name> |
| 2 | decode.field("name", decode.field("$text", decode.string)) |
Known Issues
| Issue | Platform | Impact |
|---|
| Unicode corruption in XML | Erlang | Multi-byte UTF-8 may be corrupted |
| Error message extraction | JavaScript | May return empty error details in some engines |
Error Handling
JSON Errors
gleam
| 1 | pub type JsonDecodeError { |
| 2 | UnexpectedEnd // JSON truncated |
| 3 | UnexpectedChar(String) // Invalid character (hex code) |
| 4 | UnexpectedSequence(String) |
| 5 | UnableToDecode(List(DecodeError)) // Valid JSON, wrong shape |
| 6 | } |
XML Errors
gleam
| 1 | pub type XmlDecodeError { |
| 2 | InvalidXml(String) // Malformed XML |
| 3 | UnableToDecode(List(DecodeError)) // Valid XML, wrong shape |
| 4 | } |
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
sh
| 1 | gleam test # Run the tests |
| 2 | gleam docs build # Build documentation |
Further Documentation
API documentation is available at https://hexdocs.pm/parsed_it.