Structured Data

Serialize and deserialize unstructured JSON

serde_json cat-encoding

The serde_json⮳ crate provides a serde_json::from_str⮳ function to parse a &str of JSON.

Unstructured JSON can be parsed into a universal serde_json::Value⮳ type that is able to represent any valid JSON data.

The example below shows a &str of JSON being parsed. The expected value is declared using the serde_json::json⮳ macro.

use serde_json::Error;
use serde_json::Value;
use serde_json::json;

fn main() -> Result<(), Error> {
    let j: &str = r#"{
                 "userid": 103609,
                 "verified": true,
                 "access_privileges": [
                   "user",
                   "admin"
                 ]
               }"#;

    let parsed: Value = serde_json::from_str(j)?;

    let expected: Value = json!({
        "userid": 103609,
        "verified": true,
        "access_privileges": [
            "user",
            "admin"
        ]
    });
    println!("{}", expected);
    assert_eq!(parsed, expected);

    Ok(())
}

Deserialize a TOML configuration file

toml cat-encoding

TOML is a simple, ergonomic, and readable configuration format that is often used by Rust's tooling - for example cargo. The following parses some TOML into a universal toml::Value that is able to represent any valid TOML data.

use toml::Value; /* Representation of a TOML value e.g. String,
                   * Integer, Float, Array, Table... */
use toml::de::Error;

// TOML is a minimal configuration file format.
// It is designed to map unambiguously to a hash table.

fn main() -> Result<(), Error> {
    // Sample TOML.
    // Note the use of a Rust raw string,
    // so that there is no need to escape the inner double quotes
    let toml_content = r#"
          This is a TOML comment

          key = "value"
          "quoted key" = "possible but rare"

          A table a.k.a. hash tables or dictionaries:
          [table]
          key = 123

          [strings]
          str1 = "basic string with \"escaping\" \n"
          str2 = 'literal string. No escaping is performed'
          str3 = """multi-line \
          basic string"""
          str4 = '''multi-line literal strings allow a single quote ' inside'''

          [numbers]
          int1 = 42
          hex1 = 0xdeadbeef # Or octal or binary
          float1 = 6.626e-34

          [dates]
          dt1 = 2025-02-09T00:00:00-09:00
          Dates, times, and datetimes with and without offsets are possible

          [arrays]
          basic = ["a", "b"]
          nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ]
          numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]  # Mixed-type arrays are allowed

          Dotted key notation
          table2.key = 3.14
          Equivalent to
          #[table2]
          key = 3.14

          [a.b]
          c = "1.0"
          Equivalent to a.b.c = "1.0"

          inline_table = { x = 1, y = 2 }

          [[array-of-tables]]
          name = "John"

          [[array-of-tables]]  # Empty table within the array

          [[arrays-of-tables]]
          name = "Blake"
          "#;

    let toml: Value = toml::from_str(toml_content)?;

    assert_eq!(toml["table"]["key"].as_integer(), Some(123));
    println!("{}", toml["strings"]["str1"].as_str().unwrap());
    Ok(())
}

Parse TOML into your own structs using serde⮳.

use std::collections::HashMap;

use serde::Deserialize;
use toml::de::Error;

#[derive(Deserialize, Debug)]
struct Config {
    package: Package,
    dependencies: HashMap<String, String>,
}

#[derive(Deserialize, Debug)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
}

fn main() -> Result<(), Error> {
    let toml_content = r#"
          [package]
          name = "your_package"
          version = "0.1.0"
          authors = ["You! <you@example.org>"]

          [dependencies]
          serde = "1.0"
          "#;

    let package_info: Config = toml::from_str(toml_content)?;
    println!("{:?}", package_info);

    assert_eq!(package_info.package.name, "your_package");
    assert_eq!(package_info.package.version, "0.1.0");
    assert_eq!(package_info.package.authors, vec!["You! <you@example.org>"]);
    assert_eq!(package_info.dependencies["serde"], "1.0");

    Ok(())
}

Read and write integers in little-endian byte order

byteorder byteorder-crates.io byteorder-github byteorder-lib.rs cat-encoding cat-no-std cat-parsing

byteorder⮳ is a library for reading/writing numbers in big-endian and little-endian. It can reverse the significant bytes of structured data. This may be necessary when receiving information over the network, when bytes received are from another system.

use std::io::Error;

use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
use byteorder::WriteBytesExt;

#[derive(Default, PartialEq, Debug)]
struct Payload {
    kind: u8,
    value: u16,
}

fn encode(payload: &Payload) -> Result<Vec<u8>, Error> {
    let mut bytes = vec![];
    bytes.write_u8(payload.kind)?;
    bytes.write_u16::<LittleEndian>(payload.value)?;
    Ok(bytes)
}

fn decode(mut bytes: &[u8]) -> Result<Payload, Error> {
    let payload = Payload {
        kind: bytes.read_u8()?,
        value: bytes.read_u16::<LittleEndian>()?,
    };
    Ok(payload)
}

fn main() -> Result<(), Error> {
    let original_payload = Payload::default();
    let encoded_bytes = encode(&original_payload)?;
    println!("{:?}", encoded_bytes);
    let decoded_payload = decode(&encoded_bytes)?;
    assert_eq!(original_payload, decoded_payload);
    println!("{:?}", decoded_payload);
    Ok(())
}