json_schema 1.8.0

Generated types based on the JSON-Schema for json_schema
Documentation
const std = @import("std");
const schema = @import("schema.zig");

const json = std.json;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const Value = json.Value;

fn parseBytesToValue(allocator: Allocator, bytes: []const u8) !json.Parsed(Value) {
    return json.parseFromSlice(Value, allocator, bytes, .{
        .duplicate_field_behavior = .use_last,
        .allocate = .alloc_always,
    });
}

fn valuesEqual(a: Value, b: Value) bool {
    switch (a) {
        .null => return b == .null,
        .bool => |lhs| return switch (b) {
            .bool => |rhs| lhs == rhs,
            else => false,
        },
        .integer => |lhs| return numbersEqual(@as(f64, @floatFromInt(lhs)), b),
        .float => |lhs| return numbersEqual(lhs, b),
        .number_string => |lhs| {
            const parsed = parseNumberString(lhs) catch return false;
            return numbersEqual(parsed, b);
        },
        .string => |lhs| return switch (b) {
            .string => |rhs| std.mem.eql(u8, lhs, rhs),
            else => false,
        },
        .array => |lhs| return switch (b) {
            .array => |rhs| arraysEqual(lhs.items, rhs.items),
            else => false,
        },
        .object => |lhs| return switch (b) {
            .object => |rhs| objectsEqual(lhs, rhs),
            else => false,
        },
    }
}

fn numbersEqual(lhs: f64, value: Value) bool {
    switch (value) {
        .integer => |rhs| return lhs == @as(f64, @floatFromInt(rhs)),
        .float => |rhs| return lhs == rhs,
        .number_string => |rhs| {
            const parsed = parseNumberString(rhs) catch return false;
            return lhs == parsed;
        },
        else => return false,
    }
}

fn parseNumberString(text: []const u8) !f64 {
    return std.fmt.parseFloat(f64, text);
}

fn arraysEqual(a: []Value, b: []Value) bool {
    if (a.len != b.len) return false;
    var i: usize = 0;
    while (i < a.len) : (i += 1) {
        if (!valuesEqual(a[i], b[i])) return false;
    }
    return true;
}

fn objectsEqual(a: json.ObjectMap, b: json.ObjectMap) bool {
    if (a.count() != b.count()) return false;
    var it = a.iterator();
    while (it.next()) |entry| {
        const key = entry.key_ptr.*;
        const other = b.get(key) orelse return false;
        if (!valuesEqual(entry.value_ptr.*, other)) return false;
    }
    return true;
}

fn expectJsonEqual(allocator: Allocator, left: []const u8, right: []const u8) !void {
    var parsed_left = try parseBytesToValue(allocator, left);
    defer parsed_left.deinit();
    var parsed_right = try parseBytesToValue(allocator, right);
    defer parsed_right.deinit();

    try testing.expect(valuesEqual(parsed_left.value, parsed_right.value));
}

test "round trip meta-schema" {
    const allocator = testing.allocator;

    const source = try std.fs.cwd().readFileAlloc(allocator, "src/schema.json", std.math.maxInt(usize));
    defer allocator.free(source);

    var parsed = try schema.parseJSONSchema(allocator, source);
    defer parsed.deinit();

    const serialized = try schema.stringifyAlloc(allocator, parsed.value, .{});
    defer allocator.free(serialized);

    var reparsed = try schema.parseJSONSchema(allocator, serialized);
    defer reparsed.deinit();

    try expectJsonEqual(allocator, source, serialized);
}

test "modify and stringify meta-schema" {
    const allocator = testing.allocator;

    const source = try std.fs.cwd().readFileAlloc(allocator, "src/schema.json", std.math.maxInt(usize));
    defer allocator.free(source);

    var parsed = try schema.parseJSONSchema(allocator, source);
    defer parsed.deinit();

    try testing.expect(parsed.value == .object);

    const new_title = try allocator.dupe(u8, "Modified meta-schema title");
    defer allocator.free(new_title);

    const previous_title = parsed.value.object.*.title;
    parsed.value.object.*.title = new_title;

    const serialized = try schema.stringifyAlloc(allocator, parsed.value, .{});
    defer allocator.free(serialized);

    var reparsed = try parseBytesToValue(allocator, serialized);
    defer reparsed.deinit();

    const title_value = reparsed.value.object.get("title") orelse {
        try testing.expect(false);
        return;
    };

    switch (title_value) {
        .string => |title_text| try testing.expect(std.mem.eql(u8, title_text, new_title)),
        else => try testing.expect(false),
    }

    // Restore previous state to avoid dangling references before allocator cleanup.
    parsed.value.object.*.title = previous_title;
}