Show HN: Realization Jsmn on a Pure Zig

1 month ago 5

A lean, mean, JSON-parsing machine that doesn't believe in bloat.

Zig License

Ever felt like JSON parsers are over-engineered kitchen sinks that do everything except make coffee? Meet jsmn_zig - a spiritual port of the legendary JSMN to Zig, with some Zig-ish improvements.

We don't parse JSON into fancy ASTs. We give you tokens. You decide what to do with them. It's like IKEA furniture for JSON - we give you the pieces, you build the masterpiece.

Features That Don't Waste Your Time

  • 🎯 Zero-copy tokenization - Because copying data is so 1990s
  • 📦 Compact tokens - 32 bits per token (when we can get away with it)
  • 🌊 Streaming support - Parse chunks like a boss
  • 🧠 Hybrid memory - Stack for small stuff, heap when you're feeling fancy
  • ⚡ SIMD-ready - Because we like going fast
  • 📱 Embedded-friendly - Works where other parsers would cry

Quick Start (For the Impatient)

const jsmn = @import("jsmn_zig.zig"); // The lazy way (we won't judge) const Parser = jsmn.Jsmn(jsmn.jsmn_default_config()); var tokens: [32]Parser.Token = undefined; var parents: [32]Parser.IndexT = undefined; const json = "{\"answer\": 42}"; const count = try Parser.parseTokens(&tokens, &parents, json); // Look ma, no allocations! std.debug.print("Found {} tokens\n", .{count});
const cfg = jsmn.jsmn_default_config(); cfg.compact_tokens = true; // Save those precious bytes cfg.max_depth = 256; // Don't trust deep JSON? We get it. const Parser = jsmn.Jsmn(cfg); // Let the parser handle memory like a responsible adult const result = try Parser.parseHybrid(allocator, giant_json_string); defer result.deinit(allocator); // Do something cool with your tokens for (result.heap_slice) |token| { const slice = giant_json_string[token.getStart()..token.getEnd()]; std.debug.print("Token: {s}\n", .{slice}); }

Configuration Options (Because Choice is Good)

const config = jsmn.jsmn_default_config(); config.compact_tokens = true; // Pack tokens into 32 bits config.max_depth = 1024; // Prevent stack overflow (the bad kind) config.tiny_mode = false; // Strip helpers for maximum minimalism config.enable_helpers = true; // Keep the training wheels on config.use_simd = null; // Let us guess what's best for your CPU

API Highlights (The Good Parts)

// The workhorse try Parser.parseTokens(tokens, parents, input); // For when you're dealing with streams var state = Parser.ParserState{}; try Parser.parseChunk(&state, tokens, parents, chunk1, false); try Parser.parseChunk(&state, tokens, parents, chunk2, true);
// Stack-only (for the purists) const direct = try Parser.parseDirect(64, small_json); // Hybrid approach (for the pragmatic) const hybrid = try Parser.parseHybrid(allocator, unpredictable_json);

Helpers (Because Sometimes You're Lazy)

// Find values without the hassle if (Parser.findObjectValue(tokens, count, json, "username")) |value_idx| { const username = Parser.tokenText(tokens[value_idx], json); } // Iterate arrays like it's nobody's business const items = Parser.getArrayItems(tokens, array_index);

Performance Notes (The Boring But Important Part)

  • Compact tokens save ~12 bytes per token vs standard tokens
  • Stack parsing avoids heap allocations for JSON < ~512 tokens
  • SIMD parsing kicks in automatically on supported architectures
  • Zero-copy means we're basically cheating at performance

When to Use This (And When Not To)

  • Embedded systems (where every byte counts)
  • High-performance parsing (when you need speed)
  • Streaming JSON (network, files, carrier pigeons)
  • Educational purposes (learn how parsing actually works)
  • JSON schema validation (we just tokenize, bro)
  • DOM-style manipulation (build your own tree)
  • People who want magic (we're wizards, not magicians)

Found a bug? Have an improvement? We love those!

  1. Fork it
  2. Fix it
  3. Test it (zig test jsmn_zig.zig)
  4. PR it

MIT - because sharing is caring, and lawyers are expensive.


Built with ❤️ and enough coffee to power a small European country.

Read Entire Article