Created
February 16, 2025 16:41
-
-
Save multivac61/10a8a955fe664d0a7212ff27ff666ecb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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