# parsed_it
[](https://hex.pm/packages/parsed_it)
[](https://hexdocs.pm/parsed_it/)
A parsing and serialization library for JSON and XML in Gleam, supporting both
Erlang and JavaScript targets.
## Installation
```sh
gleam add parsed_it@0.1
```
## Quick Start
### JSON Parsing
```gleam
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
```gleam
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
```gleam
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:
- `parsed_it/json` - JSON parsing and serialization
- `parsed_it/xml` - XML parsing and serialization
Import modules like this:
```gleam
import parsed_it/json
import parsed_it/xml
```
## API Design
### Type-Safe Parsing with `parse`
The primary API uses labeled arguments for clarity:
```gleam
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:
```gleam
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:
```gleam
// XML: <book id="123"><title>Hello</title></book>
// Becomes:
// {
// "$tag": "book",
// "$attrs": { "id": "123" },
// "title": { "$tag": "title", "$text": "Hello" }
// }
```
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
// 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:
```gleam
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`:
```gleam
// To decode: <name>Lucy</name>
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
pub type JsonDecodeError {
UnexpectedEnd // JSON truncated
UnexpectedChar(String) // Invalid character (hex code)
UnexpectedSequence(String)
UnableToDecode(List(DecodeError)) // Valid JSON, wrong shape
}
```
### XML Errors
```gleam
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
```sh
gleam test # Run the tests
gleam docs build # Build documentation
```
## Further Documentation
API documentation is available at <https://hexdocs.pm/parsed_it>.