Skip to content

Instantly share code, notes, and snippets.

@multivac61
Created February 16, 2025 16:41
Show Gist options
  • Save multivac61/10a8a955fe664d0a7212ff27ff666ecb to your computer and use it in GitHub Desktop.
Save multivac61/10a8a955fe664d0a7212ff27ff666ecb to your computer and use it in GitHub Desktop.
const std = @import("std");
const testing = std.testing;
fn writeFieldName(writer: anytype, name: []const u8) !void {
// For field names, we need to check if it needs to be quoted with @""
const needs_escaping = blk: {
if (name.len == 0) break :blk true;
// Check if the name is a valid Zig identifier
for (name, 0..) |c, i| {
if (i == 0) {
if (!std.ascii.isAlphabetic(c) and c != '_') break :blk true;
} else {
if (!std.ascii.isAlphanumeric(c) and c != '_') break :blk true;
}
}
break :blk false;
};
if (needs_escaping) {
try writer.print("@\"{s}\"", .{name});
} else {
try writer.writeAll(name);
}
}
fn writeValue(writer: anytype, value: std.json.Value) !void {
switch (value) {
.null => try writer.writeAll("null"),
.bool => |b| try writer.print("{}", .{b}),
.integer => |i| try writer.print("{}", .{i}),
.float => |f| try writer.print("{d}", .{f}),
.number_string => |n| try writer.writeAll(n),
.string => |s| {
try writer.writeAll("\"");
for (s) |c| {
switch (c) {
// Escape control characters
0x00...0x1F => try writer.print("\\u{{{x}}}", .{c}),
// Escape backslashes and quotes
'\\', '"' => try writer.print("\\{c}", .{c}),
// Print all other characters as-is
else => try writer.writeByte(c),
}
}
try writer.writeAll("\"");
},
.array => |array| {
try writer.writeAll(".{");
for (array.items, 0..) |item, i| {
try writeValue(writer, item);
if (i < array.items.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("}");
},
.object => |obj| {
try writer.writeAll(".{");
var it = obj.iterator();
var first = true;
while (it.next()) |entry| {
if (!first) try writer.writeAll(", ");
first = false;
try writer.writeAll(".");
try writeFieldName(writer, entry.key_ptr.*);
try writer.writeAll(" = ");
try writeValue(writer, entry.value_ptr.*);
}
try writer.writeAll("}");
},
}
}
fn jsonToZon(allocator: std.mem.Allocator, json_str: []const u8) ![]u8 {
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, json_str, .{});
defer parsed.deinit();
var result = std.ArrayList(u8).init(allocator);
errdefer result.deinit();
try writeValue(result.writer(), parsed.value);
try result.append('\n');
return result.toOwnedSlice();
}
test "simple values" {
const allocator = testing.allocator;
// Test null
{
const result = try jsonToZon(allocator, "null");
defer allocator.free(result);
try testing.expectEqualStrings("null\n", result);
}
// Test boolean
{
const result = try jsonToZon(allocator, "true");
defer allocator.free(result);
try testing.expectEqualStrings("true\n", result);
}
// Test integer
{
const result = try jsonToZon(allocator, "42");
defer allocator.free(result);
try testing.expectEqualStrings("42\n", result);
}
// Test float
{
const result = try jsonToZon(allocator, "3.14");
defer allocator.free(result);
try testing.expectEqualStrings("3.14\n", result);
}
// Test string
{
const result = try jsonToZon(allocator, "\"hello\"");
defer allocator.free(result);
try testing.expectEqualStrings("\"hello\"\n", result);
}
}
test "arrays" {
const allocator = testing.allocator;
// Empty array
{
const result = try jsonToZon(allocator, "[]");
defer allocator.free(result);
try testing.expectEqualStrings(".{}\n", result);
}
// Simple array
{
const result = try jsonToZon(allocator, "[1,2,3]");
defer allocator.free(result);
try testing.expectEqualStrings(".{1, 2, 3}\n", result);
}
// Mixed array
{
const result = try jsonToZon(allocator, "[1,\"two\",true,null]");
defer allocator.free(result);
try testing.expectEqualStrings(".{1, \"two\", true, null}\n", result);
}
}
test "objects" {
const allocator = testing.allocator;
// Empty object
{
const result = try jsonToZon(allocator, "{}");
defer allocator.free(result);
try testing.expectEqualStrings(".{}\n", result);
}
// Simple object
{
const result = try jsonToZon(allocator, "{\"name\":\"test\",\"value\":42}");
defer allocator.free(result);
try testing.expectEqualStrings(".{.name = \"test\", .value = 42}\n", result);
}
// Object with special characters in keys
{
const result = try jsonToZon(allocator, "{\"special-key\":123,\"normal_key\":456}");
defer allocator.free(result);
try testing.expectEqualStrings(".{.@\"special-key\" = 123, .normal_key = 456}\n", result);
}
}
test "nested structures" {
const allocator = testing.allocator;
// Nested object
{
const json =
\\{
\\ "outer": {
\\ "inner": {
\\ "value": 42
\\ }
\\ }
\\}
;
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings(".{.outer = .{.inner = .{.value = 42}}}\n", result);
}
// Nested array
{
const json = "[[1,2],[3,4]]";
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings(".{.{1, 2}, .{3, 4}}\n", result);
}
// Complex nested structure
{
const json =
\\{
\\ "name": "test",
\\ "data": {
\\ "numbers": [1,2,3],
\\ "metadata": {
\\ "active": true,
\\ "tags": ["a","b"]
\\ }
\\ }
\\}
;
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings(
\\.{.name = "test", .data = .{.numbers = .{1, 2, 3}, .metadata = .{.active = true, .tags = .{"a", "b"}}}}
\\
, result);
}
}
test "special cases" {
const allocator = testing.allocator;
// Empty string key
{
const result = try jsonToZon(allocator, "{\"\":123}");
defer allocator.free(result);
try testing.expectEqualStrings(".{.@\"\" = 123}\n", result);
}
// Escaped control characters in strings
{
const json = "\"\\u0001\\u0002\\u0003\"";
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings("\"\\u{1}\\u{2}\\u{3}\"\n", result);
}
// Numbers at different scales
{
const json = "[1e-10, 1e10, -123.456]";
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings(".{0.0000000001, 10000000000, -123.456}\n", result);
}
}
test "patch file conversion" {
const allocator = testing.allocator;
const json =
\\{
\\ "version": "2.4.1",
\\ "zoom": 1.0,
\\ "gridOffset": [
\\ -2.0,
\\ -0.078947365283966064
\\ ],
\\ "modules": [
\\ {
\\ "id": 8601159184541723,
\\ "plugin": "Fundamental",
\\ "model": "VCO",
\\ "version": "2.0",
\\ "params": [
\\ {
\\ "value": 0.0,
\\ "id": 0
\\ },
\\ {
\\ "value": 1.0,
\\ "id": 1
\\ }
\\ ],
\\ "leftModuleId": 2,
\\ "rightModuleId": 5726895899473528,
\\ "pos": [
\\ 9,
\\ 0
\\ ]
\\ }
\\ ],
\\ "cables": [
\\ {
\\ "id": 5155876120487880,
\\ "outputModuleId": 2,
\\ "outputId": 1,
\\ "inputModuleId": 5726895899473528,
\\ "inputId": 4,
\\ "color": "#ff9352"
\\ }
\\ ]
\\}
;
const expected_zon =
\\.{.version = "2.4.1", .zoom = 1, .gridOffset = .{-2, -0.07894736528396606}, .modules = .{.{.id = 8601159184541723, .plugin = "Fundamental", .model = "VCO", .version = "2.0", .params = .{.{.value = 0, .id = 0}, .{.value = 1, .id = 1}}, .leftModuleId = 2, .rightModuleId = 5726895899473528, .pos = .{9, 0}}}, .cables = .{.{.id = 5155876120487880, .outputModuleId = 2, .outputId = 1, .inputModuleId = 5726895899473528, .inputId = 4, .color = "#ff9352"}}}
\\
;
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings(expected_zon, result);
}
test "full patch file conversion" {
const allocator = testing.allocator;
const json =
\\{
\\ "version": "2.4.1",
\\ "zoom": 1.0,
\\ "gridOffset": [
\\ -2.0,
\\ -0.078947365283966064
\\ ],
\\ "modules": [
\\ {
\\ "id": 8601159184541723,
\\ "plugin": "Fundamental",
\\ "model": "VCO",
\\ "version": "2.0",
\\ "params": [
\\ {
\\ "value": 0.0,
\\ "id": 0
\\ },
\\ {
\\ "value": 1.0,
\\ "id": 1
\\ },
\\ {
\\ "value": 0.0,
\\ "id": 2
\\ },
\\ {
\\ "value": 0.0,
\\ "id": 3
\\ },
\\ {
\\ "value": 0.0,
\\ "id": 4
\\ },
\\ {
\\ "value": 0.5,
\\ "id": 5
\\ },
\\ {
\\ "value": 0.0,
\\ "id": 6
\\ },
\\ {
\\ "value": 0.0,
\\ "id": 7
\\ }
\\ ],
\\ "leftModuleId": 2,
\\ "rightModuleId": 5726895899473528,
\\ "pos": [
\\ 9,
\\ 0
\\ ]
\\ }
\\ ],
\\ "cables": [
\\ {
\\ "id": 5155876120487880,
\\ "outputModuleId": 2,
\\ "outputId": 1,
\\ "inputModuleId": 5726895899473528,
\\ "inputId": 4,
\\ "color": "#ff9352"
\\ },
\\ {
\\ "id": 781753834216137,
\\ "outputModuleId": 2,
\\ "outputId": 6,
\\ "inputModuleId": 5726895899473528,
\\ "inputId": 5,
\\ "color": "#ffd452"
\\ }
\\ ]
\\}
;
const expected_zon =
\\.{.version = "2.4.1", .zoom = 1, .gridOffset = .{-2, -0.07894736528396606}, .modules = .{.{.id = 8601159184541723, .plugin = "Fundamental", .model = "VCO", .version = "2.0", .params = .{.{.value = 0, .id = 0}, .{.value = 1, .id = 1}, .{.value = 0, .id = 2}, .{.value = 0, .id = 3}, .{.value = 0, .id = 4}, .{.value = 0.5, .id = 5}, .{.value = 0, .id = 6}, .{.value = 0, .id = 7}}, .leftModuleId = 2, .rightModuleId = 5726895899473528, .pos = .{9, 0}}}, .cables = .{.{.id = 5155876120487880, .outputModuleId = 2, .outputId = 1, .inputModuleId = 5726895899473528, .inputId = 4, .color = "#ff9352"}, .{.id = 781753834216137, .outputModuleId = 2, .outputId = 6, .inputModuleId = 5726895899473528, .inputId = 5, .color = "#ffd452"}}}
\\
;
const result = try jsonToZon(allocator, json);
defer allocator.free(result);
try testing.expectEqualStrings(expected_zon, result);
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len < 2) {
std.debug.print("Usage: {s} <json-file>\n", .{args[0]});
return error.MissingArgument;
}
const json_file_path = args[1];
const file = try std.fs.cwd().openFile(json_file_path, .{});
defer file.close();
const json_str = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(json_str);
const zon = try jsonToZon(allocator, json_str);
defer allocator.free(zon);
const stdout = std.io.getStdOut().writer();
try stdout.writeAll(zon);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment