summaryrefslogtreecommitdiff
path: root/src/json.zig
blob: ff07531dc5b9842e39a40d693f1ca5cf548d48c2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// TODO: ALSO IMPLEMENT jsonStringify !!!

const std = @import("std");

const Allocator = std.mem.Allocator;
const ParseError = std.json.ParseError;
const ParseFromValueError = std.json.ParseFromValueError;
const ParseOptions = std.json.ParseOptions;
const Value = std.json.Value;

pub fn JsonParse(T: type) type {
    const f = struct {
        pub fn f(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!T {
            _ = allocator;
            _ = options;
            unreachable;
        }
    }.f;
    return @TypeOf(f);
}

pub fn makeJsonParse(T: type) JsonParse(T) {
    comptime if (!std.meta.hasFn(T, "jsonParseFromValue")) {
        @compileError("Did you forget `pub const jsonParseFromValue = json.makeJsonParseFromValue(" ++ @typeName(T) ++ ") ?");
    };

    return struct {
        pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!T {
            const value = try std.json.innerParse(Value, allocator, source, options);
            return try T.jsonParseFromValue(allocator, value, options);
        }
    }.jsonParse;
}

pub fn JsonParseFromValue(T: type) type {
    const f = struct {
        pub fn f(allocator: Allocator, source: Value, options: ParseOptions) ParseFromValueError!T {
            _ = allocator;
            _ = source;
            _ = options;
            unreachable;
        }
    }.f;
    return @TypeOf(f);
}

pub fn makeJsonParseFromValue(T: type) JsonParseFromValue(T) {
    return makeJsonParseFromValueWithTag(T, "type");
}

pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonParseFromValue(T) {
    const union_info = switch (@typeInfo(T)) {
        .@"union" => |info| blk: {
            if (info.tag_type == null) {
                @compileError("Only tagged unions supported, got '" ++ @typeName(T) ++ "'");
            }
            break :blk info;
        },
        else => @compileError("Only unions supported, got '" ++ @typeName(T) ++ "'"),
    };

    const TagType = union_info.tag_type.?;

    const Base = blk: {
        const info = std.builtin.Type{ .@"struct" = .{
            .layout = .auto,
            .fields = &.{.{
                .name = tag,
                .type = TagType,
                .default_value_ptr = null,
                .is_comptime = false,
                .alignment = 0,
            }},
            .decls = &.{},
            .is_tuple = false,
        } };
        break :blk @Type(info);
    };

    return struct {
        pub fn jsonParseFromValue(
            allocator: Allocator,
            source: Value,
            options: ParseOptions,
        ) ParseFromValueError!T {
            const new_options = blk: {
                var opt = options;
                opt.ignore_unknown_fields = true;
                break :blk opt;
            };

            const base = try std.json.innerParseFromValue(Base, allocator, source, new_options);
            const typeName = @tagName(@field(base, tag));
            // TODO: Upgrade to StaticStringMap
            inline for (union_info.fields) |field_info| {
                if (std.mem.eql(u8, typeName, field_info.name)) {
                    return @unionInit(
                        T,
                        field_info.name,
                        try std.json.innerParseFromValue(field_info.type, allocator, source, new_options),
                    );
                }
            }

            unreachable;
        }
    }.jsonParseFromValue;
}