{"action":"created","comment":{"url":"https://api.github.com/repos/zig-lang/zig/pulls/comments/185402689","pull_request_review_id":116781500,"id":185402689,"diff_hunk":"@@ -0,0 +1,1308 @@\n+// JSON parser conforming to RFC8259.\n+//\n+// https://tools.ietf.org/html/rfc8259\n+\n+const std = @import(\"index.zig\");\n+const mem = std.mem;\n+\n+const u1 = @IntType(false, 1);\n+const u256 = @IntType(false, 256);\n+\n+pub const TokenId = enum(u5) {\n+    ObjectBegin,\n+    ObjectEnd,\n+    ArrayBegin,\n+    ArrayEnd,\n+    String,\n+    Number,\n+    True,\n+    False,\n+    Null,\n+};\n+\n+// A single token slice into the parent string.\n+//\n+// Use `token.slice()` on the inptu at the current position to get the current slice.\n+pub const Token = struct {\n+    id: TokenId,\n+    // How many bytes do we skip before counting\n+    offset: u1,\n+    // Whether string contains a \\uXXXX sequence and cannot be zero-copied\n+    string_has_unicode_escape: bool,\n+    // Whether number is simple and can be represented by an integer (i.e. no `.` or `e`)\n+    number_is_integer: bool,\n+    // How many bytes from the current position behind the start of this token is.\n+    count: usize,\n+\n+    pub fn init(id: TokenId, count: usize, offset: u1) Token {\n+        return Token {\n+            .id = id,\n+            .offset = offset,\n+            .string_has_unicode_escape = false,\n+            .number_is_integer = true,\n+            .count = count,\n+        };\n+    }\n+\n+    pub fn initString(count: usize, has_unicode_escape: bool) Token {\n+        return Token {\n+            .id = TokenId.String,\n+            .offset = 0,\n+            .string_has_unicode_escape = has_unicode_escape,\n+            .number_is_integer = true,\n+            .count = count,\n+        };\n+    }\n+\n+    pub fn initNumber(count: usize, number_is_integer: bool) Token {\n+        return Token {\n+            .id = TokenId.Number,\n+            .offset = 0,\n+            .string_has_unicode_escape = false,\n+            .number_is_integer = number_is_integer,\n+            .count = count,\n+        };\n+    }\n+\n+    // A marker token is a zero-length\n+    pub fn initMarker(id: TokenId) Token {\n+        return Token {\n+            .id = id,\n+            .offset = 0,\n+            .string_has_unicode_escape = false,\n+            .number_is_integer = true,\n+            .count = 0,\n+        };\n+    }\n+\n+    // Slice into the underlying input string.\n+    pub fn slice(self: &const Token, input: []const u8, i: usize) []const u8 {\n+        return input[i + self.offset - self.count .. i + self.offset];\n+    }\n+};\n+\n+// A small streaming JSON parser. This accepts input one byte at a time and returns tokens as\n+// they are encountered. No copies or allocations are performed during parsing and the entire\n+// parsing state requires ~40-50 bytes of stack space.\n+//\n+// Conforms strictly to RFC8529.\n+const StreamingJsonParser = struct {\n+    // Current state\n+    state: State,\n+    // How many bytes we have counted for the current token\n+    count: usize,\n+    // What state to follow after parsing a string (either property or value string)\n+    after_string_state: State,\n+    // What state to follow after parsing a value (either top-level or value end)\n+    after_value_state: State,\n+    // If we stopped now, would the complete parsed string to now be a valid json string\n+    complete: bool,\n+    // Current token flags to pass through to the next generated, see Token.\n+    string_has_unicode_escape: bool,\n+    number_is_integer: bool,\n+\n+    // Bit-stack for nested object/map literals (max 255 nestings).\n+    stack: u256,\n+    stack_used: u8,\n+\n+    const object_bit     = 0;\n+    const array_bit      = 1;\n+    const max_stack_size = @maxValue(u8);\n+\n+    pub fn init() StreamingJsonParser {\n+        var p: StreamingJsonParser = undefined;\n+        p.reset();\n+        return p;\n+    }\n+\n+    pub fn reset(p: &StreamingJsonParser) void {\n+        p.state = State.TopLevelBegin;\n+        p.count = 0;\n+        // Set before ever read in main transition function\n+        p.after_string_state = undefined;\n+        p.after_value_state = State.ValueEnd;   // handle end of values normally\n+        p.stack = 0;\n+        p.stack_used = 0;\n+        p.complete = false;\n+        p.string_has_unicode_escape = false;\n+        p.number_is_integer = true;\n+    }\n+\n+    pub const State = enum {\n+        // NOTE: @tagName determines the name based on index, not value.\n+\n+        // These must be first with these explicit values as we rely on them for indexing the\n+        // bit-stack directly and avoiding a branch.\n+        ObjectSeparator = 0,\n+        ValueEnd = 1,\n+\n+        TopLevelBegin,\n+        TopLevelEnd,\n+\n+        ValueBegin,\n+        ValueBeginNoClosing,\n+\n+        String,\n+        StringUtf8Byte3,\n+        StringUtf8Byte2,\n+        StringUtf8Byte1,\n+        StringEscapeCharacter,\n+        StringEscapeHexUnicode4,\n+        StringEscapeHexUnicode3,\n+        StringEscapeHexUnicode2,\n+        StringEscapeHexUnicode1,\n+\n+        Number,\n+        NumberMaybeDotOrExponent,\n+        NumberMaybeDigitOrDotOrExponent,\n+        NumberFractionalRequired,\n+        NumberFractional,\n+        NumberMaybeExponent,\n+        NumberExponent,\n+        NumberExponentDigitsRequired,\n+        NumberExponentDigits,\n+\n+        TrueLiteral1,\n+        TrueLiteral2,\n+        TrueLiteral3,\n+\n+        FalseLiteral1,\n+        FalseLiteral2,\n+        FalseLiteral3,\n+        FalseLiteral4,\n+\n+        NullLiteral1,\n+        NullLiteral2,\n+        NullLiteral3,\n+\n+        // Only call this function to generate array/object final state.\n+        pub fn fromInt(x: var) State {\n+            std.debug.assert(x == 0 or x == 1);\n+            return State(u6(x));\n+        }\n+    };\n+\n+    pub const Error = error {\n+        InvalidTopLevel,\n+        TooManyNestedItems,\n+        TooManyClosingItems,\n+        InvalidValueBegin,\n+        InvalidValueEnd,\n+        UnbalancedBrackets,\n+        UnbalancedBraces,\n+        UnexpectedClosingBracket,\n+        UnexpectedClosingBrace,\n+        InvalidNumber,\n+        InvalidSeparator,\n+        InvalidLiteral,\n+        InvalidEscapeCharacter,\n+        InvalidUnicodeHexSymbol,\n+        InvalidUtf8Byte,\n+        InvalidTopLevelTrailing,\n+        InvalidControlCharacter,\n+    };\n+\n+    // Give another byte to the parser and obtain any new tokens. This may (rarely) return two\n+    // tokens. token2 is always null if token1 is null.\n+    //\n+    // There is currently no error recovery on a bad stream.\n+    pub fn feed(p: &StreamingJsonParser, c: u8, token1: &?Token, token2: &?Token) Error!void {\n+        *token1 = null;\n+        *token2 = null;\n+        p.count += 1;\n+\n+        // unlikely\n+        if (try p.transition(c, token1)) {\n+            _ = try p.transition(c, token2);\n+        }\n+    }\n+\n+    // Perform a single transition on the state machine and return any possible token.\n+    fn transition(p: &StreamingJsonParser, c: u8, token: &?Token) Error!bool {\n+        switch (p.state) {\n+            State.TopLevelBegin => switch (c) {\n+                '{' => {\n+                    p.stack <<= 1;\n+                    p.stack |= object_bit;\n+                    p.stack_used += 1;\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ObjectSeparator;\n+\n+                    *token = Token.initMarker(TokenId.ObjectBegin);\n+                },\n+                '[' => {\n+                    p.stack <<= 1;\n+                    p.stack |= array_bit;\n+                    p.stack_used += 1;\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ValueEnd;\n+\n+                    *token = Token.initMarker(TokenId.ArrayBegin);\n+                },\n+                '-' => {\n+                    p.number_is_integer = true;\n+                    p.state = State.Number;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    p.count = 0;\n+                },\n+                '0' => {\n+                    p.number_is_integer = true;\n+                    p.state = State.NumberMaybeDotOrExponent;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    p.count = 0;\n+                },\n+                '1' ... '9' => {\n+                    p.number_is_integer = true;\n+                    p.state = State.NumberMaybeDigitOrDotOrExponent;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    p.count = 0;\n+                },\n+                '\"' => {\n+                    p.state = State.String;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    // We don't actually need the following since after_value_state should override.\n+                    p.after_string_state = State.ValueEnd;\n+                    p.string_has_unicode_escape = false;\n+                    p.count = 0;\n+                },\n+                't' => {\n+                    p.state = State.TrueLiteral1;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    p.count = 0;\n+                },\n+                'f' => {\n+                    p.state = State.FalseLiteral1;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    p.count = 0;\n+                },\n+                'n' => {\n+                    p.state = State.NullLiteral1;\n+                    p.after_value_state = State.TopLevelEnd;\n+                    p.count = 0;\n+                },\n+                0x09, 0x0A, 0x0D, 0x20 => {\n+                    // whitespace\n+                },\n+                else => {\n+                    return error.InvalidTopLevel;\n+                },\n+            },\n+\n+            State.TopLevelEnd => switch (c) {\n+                0x09, 0x0A, 0x0D, 0x20 => {\n+                    // whitespace\n+                },\n+                else => {\n+                    return error.InvalidTopLevelTrailing;\n+                },\n+            },\n+\n+            State.ValueBegin => switch (c) {\n+                // NOTE: These are shared in ValueEnd as well, think we can reorder states to\n+                // be a bit clearer and avoid this duplication.\n+                '}' => {\n+                    // unlikely\n+                    if (p.stack & 1 != object_bit) {\n+                        return error.UnexpectedClosingBracket;\n+                    }\n+                    if (p.stack_used == 0) {\n+                        return error.TooManyClosingItems;\n+                    }\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.fromInt(p.stack & 1);\n+\n+                    p.stack >>= 1;\n+                    p.stack_used -= 1;\n+\n+                    switch (p.stack_used) {\n+                        0 => {\n+                            p.complete = true;\n+                            p.state = State.TopLevelEnd;\n+                        },\n+                        else => {},\n+                    }\n+\n+                    *token = Token.initMarker(TokenId.ObjectEnd);\n+                },\n+                ']' => {\n+                    if (p.stack & 1 != array_bit) {\n+                        return error.UnexpectedClosingBrace;\n+                    }\n+                    if (p.stack_used == 0) {\n+                        return error.TooManyClosingItems;\n+                    }\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.fromInt(p.stack & 1);\n+\n+                    p.stack >>= 1;\n+                    p.stack_used -= 1;\n+\n+                    switch (p.stack_used) {\n+                        0 => {\n+                            p.complete = true;\n+                            p.state = State.TopLevelEnd;\n+                        },\n+                        else => {},\n+                    }\n+\n+                    *token = Token.initMarker(TokenId.ArrayEnd);\n+                },\n+                '{' => {\n+                    if (p.stack_used == max_stack_size) {\n+                        return error.TooManyNestedItems;\n+                    }\n+\n+                    p.stack <<= 1;\n+                    p.stack |= object_bit;\n+                    p.stack_used += 1;\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ObjectSeparator;\n+\n+                    *token = Token.initMarker(TokenId.ObjectBegin);\n+                },\n+                '[' => {\n+                    if (p.stack_used == max_stack_size) {\n+                        return error.TooManyNestedItems;\n+                    }\n+\n+                    p.stack <<= 1;\n+                    p.stack |= array_bit;\n+                    p.stack_used += 1;\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ValueEnd;\n+\n+                    *token = Token.initMarker(TokenId.ArrayBegin);\n+                },\n+                '-' => {\n+                    p.state = State.Number;\n+                    p.count = 0;\n+                },\n+                '0' => {\n+                    p.state = State.NumberMaybeDotOrExponent;\n+                    p.count = 0;\n+                },\n+                '1' ... '9' => {\n+                    p.state = State.NumberMaybeDigitOrDotOrExponent;\n+                    p.count = 0;\n+                },\n+                '\"' => {\n+                    p.state = State.String;\n+                    p.count = 0;\n+                },\n+                't' => {\n+                    p.state = State.TrueLiteral1;\n+                    p.count = 0;\n+                },\n+                'f' => {\n+                    p.state = State.FalseLiteral1;\n+                    p.count = 0;\n+                },\n+                'n' => {\n+                    p.state = State.NullLiteral1;\n+                    p.count = 0;\n+                },\n+                0x09, 0x0A, 0x0D, 0x20 => {\n+                    // whitespace\n+                },\n+                else => {\n+                    return error.InvalidValueBegin;\n+                },\n+            },\n+\n+            // TODO: A bit of duplication here and in the following state, redo.\n+            State.ValueBeginNoClosing => switch (c) {\n+                '{' => {\n+                    if (p.stack_used == max_stack_size) {\n+                        return error.TooManyNestedItems;\n+                    }\n+\n+                    p.stack <<= 1;\n+                    p.stack |= object_bit;\n+                    p.stack_used += 1;\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ObjectSeparator;\n+\n+                    *token = Token.initMarker(TokenId.ObjectBegin);\n+                },\n+                '[' => {\n+                    if (p.stack_used == max_stack_size) {\n+                        return error.TooManyNestedItems;\n+                    }\n+\n+                    p.stack <<= 1;\n+                    p.stack |= array_bit;\n+                    p.stack_used += 1;\n+\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ValueEnd;\n+\n+                    *token = Token.initMarker(TokenId.ArrayBegin);\n+                },\n+                '-' => {\n+                    p.state = State.Number;\n+                    p.count = 0;\n+                },\n+                '0' => {\n+                    p.state = State.NumberMaybeDotOrExponent;\n+                    p.count = 0;\n+                },\n+                '1' ... '9' => {\n+                    p.state = State.NumberMaybeDigitOrDotOrExponent;\n+                    p.count = 0;\n+                },\n+                '\"' => {\n+                    p.state = State.String;\n+                    p.count = 0;\n+                },\n+                't' => {\n+                    p.state = State.TrueLiteral1;\n+                    p.count = 0;\n+                },\n+                'f' => {\n+                    p.state = State.FalseLiteral1;\n+                    p.count = 0;\n+                },\n+                'n' => {\n+                    p.state = State.NullLiteral1;\n+                    p.count = 0;\n+                },\n+                0x09, 0x0A, 0x0D, 0x20 => {\n+                    // whitespace\n+                },\n+                else => {\n+                    return error.InvalidValueBegin;\n+                },\n+            },\n+\n+            State.ValueEnd => switch (c) {\n+                ',' => {\n+                    p.after_string_state = State.fromInt(p.stack & 1);\n+                    p.state = State.ValueBeginNoClosing;\n+                },\n+                ']' => {\n+                    if (p.stack_used == 0) {\n+                        return error.UnbalancedBrackets;\n+                    }\n+\n+                    p.state = State.ValueEnd;\n+                    p.after_string_state = State.fromInt(p.stack & 1);\n+\n+                    p.stack >>= 1;\n+                    p.stack_used -= 1;\n+\n+                    if (p.stack_used == 0) {\n+                        p.complete = true;\n+                        p.state = State.TopLevelEnd;\n+                    }\n+\n+                    *token = Token.initMarker(TokenId.ArrayEnd);\n+                },\n+                '}' => {\n+                    if (p.stack_used == 0) {\n+                        return error.UnbalancedBraces;\n+                    }\n+\n+                    p.state = State.ValueEnd;\n+                    p.after_string_state = State.fromInt(p.stack & 1);\n+\n+                    p.stack >>= 1;\n+                    p.stack_used -= 1;\n+\n+                    if (p.stack_used == 0) {\n+                        p.complete = true;\n+                        p.state = State.TopLevelEnd;\n+                    }\n+\n+                    *token = Token.initMarker(TokenId.ObjectEnd);\n+                },\n+                0x09, 0x0A, 0x0D, 0x20 => {\n+                    // whitespace\n+                },\n+                else => {\n+                    return error.InvalidValueEnd;\n+                },\n+            },\n+\n+            State.ObjectSeparator => switch (c) {\n+                ':' => {\n+                    p.state = State.ValueBegin;\n+                    p.after_string_state = State.ValueEnd;\n+                },\n+                0x09, 0x0A, 0x0D, 0x20 => {\n+                    // whitespace\n+                },\n+                else => {\n+                    return error.InvalidSeparator;\n+                },\n+            },\n+\n+            State.String => switch (c) {\n+                0x00 ... 0x1F => {\n+                    return error.InvalidControlCharacter;\n+                },\n+                '\"' => {\n+                    p.state = p.after_string_state;\n+                    if (p.after_value_state == State.TopLevelEnd) {\n+                        p.state = State.TopLevelEnd;\n+                        p.complete = true;\n+                    }\n+\n+                    *token = Token.initString(p.count - 1, p.string_has_unicode_escape);\n+                },\n+                '\\\\' => {\n+                    p.state = State.StringEscapeCharacter;\n+                },\n+                0x20, 0x21, 0x23 ... 0x5B, 0x5D ... 0x7F => {\n+                    // non-control ascii\n+                },\n+                0xC0 ... 0xDF => {\n+                    p.state = State.StringUtf8Byte1;\n+                },\n+                0xE0 ... 0xEF => {\n+                    p.state = State.StringUtf8Byte2;\n+                },\n+                0xF0 ... 0xFF => {\n+                    p.state = State.StringUtf8Byte3;\n+                },\n+                else => {\n+                    return error.InvalidUtf8Byte;\n+                },\n+            },\n+\n+            State.StringUtf8Byte3 => switch (c >> 6) {\n+                0b10 => p.state = State.StringUtf8Byte2,\n+                else => return error.InvalidUtf8Byte,\n+            },\n+\n+            State.StringUtf8Byte2 => switch (c >> 6) {\n+                0b10 => p.state = State.StringUtf8Byte1,\n+                else => return error.InvalidUtf8Byte,\n+            },\n+\n+            State.StringUtf8Byte1 => switch (c >> 6) {\n+                0b10 => p.state = State.String,\n+                else => return error.InvalidUtf8Byte,\n+            },\n+\n+            State.StringEscapeCharacter => switch (c) {\n+                // NOTE: '/' is allowed as an escaped character but it also is allowed\n+                // as unescaped according to the RFC. There is a reported errata which suggests\n+                // removing the non-escaped variant but it makes more sense to simply disallow\n+                // it as an escape code here.\n+                //\n+                // The current JSONTestSuite tests rely on both of this behaviour being present\n+                // however, so we default to the status quo where both are accepted until this\n+                // is further clarified.\n+                '\"', '\\\\', '/', 'b', 'f', 'n', 'r', 't' => {\n+                    p.state = State.String;","path":"std/json.zig","position":604,"original_position":604,"commit_id":"60174b5a73f9f798cda3d7d73da53bdb89317f64","original_commit_id":"60174b5a73f9f798cda3d7d73da53bdb89317f64","user":{"login":"tiehuis","id":4605603,"avatar_url":"https://avatars0.githubusercontent.com/u/4605603?v=4","gravatar_id":"","url":"https://api.github.com/users/tiehuis","html_url":"https://github.com/tiehuis","followers_url":"https://api.github.com/users/tiehuis/followers","following_url":"https://api.github.com/users/tiehuis/following{/other_user}","gists_url":"https://api.github.com/users/tiehuis/gists{/gist_id}","starred_url":"https://api.github.com/users/tiehuis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tiehuis/subscriptions","organizations_url":"https://api.github.com/users/tiehuis/orgs","repos_url":"https://api.github.com/users/tiehuis/repos","events_url":"https://api.github.com/users/tiehuis/events{/privacy}","received_events_url":"https://api.github.com/users/tiehuis/received_events","type":"User","site_admin":false},"body":"Want a `has_escape` flag set here as in the `u` unicode case.","created_at":"2018-05-02T06:25:36Z","updated_at":"2018-05-02T06:25:36Z","html_url":"https://github.com/zig-lang/zig/pull/973#discussion_r185402689","pull_request_url":"https://api.github.com/repos/zig-lang/zig/pulls/973","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/zig-lang/zig/pulls/comments/185402689"},"html":{"href":"https://github.com/zig-lang/zig/pull/973#discussion_r185402689"},"pull_request":{"href":"https://api.github.com/repos/zig-lang/zig/pulls/973"}}},"pull_request":{"url":"https://api.github.com/repos/zig-lang/zig/pulls/973","id":185344734,"html_url":"https://github.com/zig-lang/zig/pull/973","diff_url":"https://github.com/zig-lang/zig/pull/973.diff","patch_url":"https://github.com/zig-lang/zig/pull/973.patch","issue_url":"https://api.github.com/repos/zig-lang/zig/issues/973","number":973,"state":"open","locked":false,"title":"Add json decoder","user":{"login":"tiehuis","id":4605603,"avatar_url":"https://avatars0.githubusercontent.com/u/4605603?v=4","gravatar_id":"","url":"https://api.github.com/users/tiehuis","html_url":"https://github.com/tiehuis","followers_url":"https://api.github.com/users/tiehuis/followers","following_url":"https://api.github.com/users/tiehuis/following{/other_user}","gists_url":"https://api.github.com/users/tiehuis/gists{/gist_id}","starred_url":"https://api.github.com/users/tiehuis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tiehuis/subscriptions","organizations_url":"https://api.github.com/users/tiehuis/orgs","repos_url":"https://api.github.com/users/tiehuis/repos","events_url":"https://api.github.com/users/tiehuis/events{/privacy}","received_events_url":"https://api.github.com/users/tiehuis/received_events","type":"User","site_admin":false},"body":"Everyone needs a Json decoder so here is one.\r\n\r\nThis is blocked on parsing floats #375. I figured I'd push for now however since it is otherwise largely complete.\r\n\r\n### Streaming Decoder\r\n\r\nThis implements a streaming json decoder which conforms to RFC8259 (the latest spec). This decoder returns tokens indicating an event that occurs on the json stream. This is pretty much exactly the same as any other streaming parser but I've opted for the token approach versus callbacks since nobody likes callbacks.\r\n\r\nI've attempted to keep the streaming states largely branchless (although there are still a lot) where possible just because. This is also marginally more complex than normal since we decode without any lookahead. This is also the reason we have a slightly odd interface which returns possibly two tokens and not just one.\r\n\r\nThis decoder doesn't allocate any memory and should be used to build more friendly decoders.\r\n\r\n### Value/Tree Decoder\r\n\r\nI've also implemented a more standard tree decoder on top of the streaming version. This allocates nodes as needed and is useful for parsing unknown data formats.\r\n\r\n### Tests\r\n\r\nTests are taken from here: https://github.com/nst/JSONTestSuite\r\nRecommended accompanying reading: http://seriot.ch/parsing_json.php\r\n\r\n### Other Things to do\r\n\r\n - Decode Json into a struct based on it's fields. This should actually be relatively easy as the streaming parser does the majority of the work.\r\n - Benchmark to get a relative idea of the performance would be nice.\r\n - An accompanying encoder of course needs to be written. This is fairly straight-forward in comparison.\r\n\r\n","created_at":"2018-05-02T06:19:28Z","updated_at":"2018-05-02T06:25:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"108f70b6b4d68877bbbfb6e8dac3ac1ef69b4302","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/zig-lang/zig/pulls/973/commits","review_comments_url":"https://api.github.com/repos/zig-lang/zig/pulls/973/comments","review_comment_url":"https://api.github.com/repos/zig-lang/zig/pulls/comments{/number}","comments_url":"https://api.github.com/repos/zig-lang/zig/issues/973/comments","statuses_url":"https://api.github.com/repos/zig-lang/zig/statuses/60174b5a73f9f798cda3d7d73da53bdb89317f64","head":{"label":"zig-lang:json-decoder","ref":"json-decoder","sha":"60174b5a73f9f798cda3d7d73da53bdb89317f64","user":{"login":"zig-lang","id":27973237,"avatar_url":"https://avatars3.githubusercontent.com/u/27973237?v=4","gravatar_id":"","url":"https://api.github.com/users/zig-lang","html_url":"https://github.com/zig-lang","followers_url":"https://api.github.com/users/zig-lang/followers","following_url":"https://api.github.com/users/zig-lang/following{/other_user}","gists_url":"https://api.github.com/users/zig-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/zig-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zig-lang/subscriptions","organizations_url":"https://api.github.com/users/zig-lang/orgs","repos_url":"https://api.github.com/users/zig-lang/repos","events_url":"https://api.github.com/users/zig-lang/events{/privacy}","received_events_url":"https://api.github.com/users/zig-lang/received_events","type":"Organization","site_admin":false},"repo":{"id":40276274,"name":"zig","full_name":"zig-lang/zig","owner":{"login":"zig-lang","id":27973237,"avatar_url":"https://avatars3.githubusercontent.com/u/27973237?v=4","gravatar_id":"","url":"https://api.github.com/users/zig-lang","html_url":"https://github.com/zig-lang","followers_url":"https://api.github.com/users/zig-lang/followers","following_url":"https://api.github.com/users/zig-lang/following{/other_user}","gists_url":"https://api.github.com/users/zig-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/zig-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zig-lang/subscriptions","organizations_url":"https://api.github.com/users/zig-lang/orgs","repos_url":"https://api.github.com/users/zig-lang/repos","events_url":"https://api.github.com/users/zig-lang/events{/privacy}","received_events_url":"https://api.github.com/users/zig-lang/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zig-lang/zig","description":"robust, optimal, and clear programming language","fork":false,"url":"https://api.github.com/repos/zig-lang/zig","forks_url":"https://api.github.com/repos/zig-lang/zig/forks","keys_url":"https://api.github.com/repos/zig-lang/zig/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zig-lang/zig/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zig-lang/zig/teams","hooks_url":"https://api.github.com/repos/zig-lang/zig/hooks","issue_events_url":"https://api.github.com/repos/zig-lang/zig/issues/events{/number}","events_url":"https://api.github.com/repos/zig-lang/zig/events","assignees_url":"https://api.github.com/repos/zig-lang/zig/assignees{/user}","branches_url":"https://api.github.com/repos/zig-lang/zig/branches{/branch}","tags_url":"https://api.github.com/repos/zig-lang/zig/tags","blobs_url":"https://api.github.com/repos/zig-lang/zig/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zig-lang/zig/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zig-lang/zig/git/refs{/sha}","trees_url":"https://api.github.com/repos/zig-lang/zig/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zig-lang/zig/statuses/{sha}","languages_url":"https://api.github.com/repos/zig-lang/zig/languages","stargazers_url":"https://api.github.com/repos/zig-lang/zig/stargazers","contributors_url":"https://api.github.com/repos/zig-lang/zig/contributors","subscribers_url":"https://api.github.com/repos/zig-lang/zig/subscribers","subscription_url":"https://api.github.com/repos/zig-lang/zig/subscription","commits_url":"https://api.github.com/repos/zig-lang/zig/commits{/sha}","git_commits_url":"https://api.github.com/repos/zig-lang/zig/git/commits{/sha}","comments_url":"https://api.github.com/repos/zig-lang/zig/comments{/number}","issue_comment_url":"https://api.github.com/repos/zig-lang/zig/issues/comments{/number}","contents_url":"https://api.github.com/repos/zig-lang/zig/contents/{+path}","compare_url":"https://api.github.com/repos/zig-lang/zig/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zig-lang/zig/merges","archive_url":"https://api.github.com/repos/zig-lang/zig/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zig-lang/zig/downloads","issues_url":"https://api.github.com/repos/zig-lang/zig/issues{/number}","pulls_url":"https://api.github.com/repos/zig-lang/zig/pulls{/number}","milestones_url":"https://api.github.com/repos/zig-lang/zig/milestones{/number}","notifications_url":"https://api.github.com/repos/zig-lang/zig/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zig-lang/zig/labels{/name}","releases_url":"https://api.github.com/repos/zig-lang/zig/releases{/id}","deployments_url":"https://api.github.com/repos/zig-lang/zig/deployments","created_at":"2015-08-06T00:51:28Z","updated_at":"2018-05-02T01:10:39Z","pushed_at":"2018-05-02T06:19:28Z","git_url":"git://github.com/zig-lang/zig.git","ssh_url":"git@github.com:zig-lang/zig.git","clone_url":"https://github.com/zig-lang/zig.git","svn_url":"https://github.com/zig-lang/zig","homepage":"https://ziglang.org","size":16586,"stargazers_count":1291,"watchers_count":1291,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":84,"mirror_url":null,"archived":false,"open_issues_count":297,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":84,"open_issues":297,"watchers":1291,"default_branch":"master"}},"base":{"label":"zig-lang:master","ref":"master","sha":"3a8dc4e90ddf6b3dc2bdf640c89061c00eee7d45","user":{"login":"zig-lang","id":27973237,"avatar_url":"https://avatars3.githubusercontent.com/u/27973237?v=4","gravatar_id":"","url":"https://api.github.com/users/zig-lang","html_url":"https://github.com/zig-lang","followers_url":"https://api.github.com/users/zig-lang/followers","following_url":"https://api.github.com/users/zig-lang/following{/other_user}","gists_url":"https://api.github.com/users/zig-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/zig-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zig-lang/subscriptions","organizations_url":"https://api.github.com/users/zig-lang/orgs","repos_url":"https://api.github.com/users/zig-lang/repos","events_url":"https://api.github.com/users/zig-lang/events{/privacy}","received_events_url":"https://api.github.com/users/zig-lang/received_events","type":"Organization","site_admin":false},"repo":{"id":40276274,"name":"zig","full_name":"zig-lang/zig","owner":{"login":"zig-lang","id":27973237,"avatar_url":"https://avatars3.githubusercontent.com/u/27973237?v=4","gravatar_id":"","url":"https://api.github.com/users/zig-lang","html_url":"https://github.com/zig-lang","followers_url":"https://api.github.com/users/zig-lang/followers","following_url":"https://api.github.com/users/zig-lang/following{/other_user}","gists_url":"https://api.github.com/users/zig-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/zig-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zig-lang/subscriptions","organizations_url":"https://api.github.com/users/zig-lang/orgs","repos_url":"https://api.github.com/users/zig-lang/repos","events_url":"https://api.github.com/users/zig-lang/events{/privacy}","received_events_url":"https://api.github.com/users/zig-lang/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zig-lang/zig","description":"robust, optimal, and clear programming language","fork":false,"url":"https://api.github.com/repos/zig-lang/zig","forks_url":"https://api.github.com/repos/zig-lang/zig/forks","keys_url":"https://api.github.com/repos/zig-lang/zig/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zig-lang/zig/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zig-lang/zig/teams","hooks_url":"https://api.github.com/repos/zig-lang/zig/hooks","issue_events_url":"https://api.github.com/repos/zig-lang/zig/issues/events{/number}","events_url":"https://api.github.com/repos/zig-lang/zig/events","assignees_url":"https://api.github.com/repos/zig-lang/zig/assignees{/user}","branches_url":"https://api.github.com/repos/zig-lang/zig/branches{/branch}","tags_url":"https://api.github.com/repos/zig-lang/zig/tags","blobs_url":"https://api.github.com/repos/zig-lang/zig/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zig-lang/zig/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zig-lang/zig/git/refs{/sha}","trees_url":"https://api.github.com/repos/zig-lang/zig/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zig-lang/zig/statuses/{sha}","languages_url":"https://api.github.com/repos/zig-lang/zig/languages","stargazers_url":"https://api.github.com/repos/zig-lang/zig/stargazers","contributors_url":"https://api.github.com/repos/zig-lang/zig/contributors","subscribers_url":"https://api.github.com/repos/zig-lang/zig/subscribers","subscription_url":"https://api.github.com/repos/zig-lang/zig/subscription","commits_url":"https://api.github.com/repos/zig-lang/zig/commits{/sha}","git_commits_url":"https://api.github.com/repos/zig-lang/zig/git/commits{/sha}","comments_url":"https://api.github.com/repos/zig-lang/zig/comments{/number}","issue_comment_url":"https://api.github.com/repos/zig-lang/zig/issues/comments{/number}","contents_url":"https://api.github.com/repos/zig-lang/zig/contents/{+path}","compare_url":"https://api.github.com/repos/zig-lang/zig/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zig-lang/zig/merges","archive_url":"https://api.github.com/repos/zig-lang/zig/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zig-lang/zig/downloads","issues_url":"https://api.github.com/repos/zig-lang/zig/issues{/number}","pulls_url":"https://api.github.com/repos/zig-lang/zig/pulls{/number}","milestones_url":"https://api.github.com/repos/zig-lang/zig/milestones{/number}","notifications_url":"https://api.github.com/repos/zig-lang/zig/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zig-lang/zig/labels{/name}","releases_url":"https://api.github.com/repos/zig-lang/zig/releases{/id}","deployments_url":"https://api.github.com/repos/zig-lang/zig/deployments","created_at":"2015-08-06T00:51:28Z","updated_at":"2018-05-02T01:10:39Z","pushed_at":"2018-05-02T06:19:28Z","git_url":"git://github.com/zig-lang/zig.git","ssh_url":"git@github.com:zig-lang/zig.git","clone_url":"https://github.com/zig-lang/zig.git","svn_url":"https://github.com/zig-lang/zig","homepage":"https://ziglang.org","size":16586,"stargazers_count":1291,"watchers_count":1291,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":84,"mirror_url":null,"archived":false,"open_issues_count":297,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":84,"open_issues":297,"watchers":1291,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/zig-lang/zig/pulls/973"},"html":{"href":"https://github.com/zig-lang/zig/pull/973"},"issue":{"href":"https://api.github.com/repos/zig-lang/zig/issues/973"},"comments":{"href":"https://api.github.com/repos/zig-lang/zig/issues/973/comments"},"review_comments":{"href":"https://api.github.com/repos/zig-lang/zig/pulls/973/comments"},"review_comment":{"href":"https://api.github.com/repos/zig-lang/zig/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/zig-lang/zig/pulls/973/commits"},"statuses":{"href":"https://api.github.com/repos/zig-lang/zig/statuses/60174b5a73f9f798cda3d7d73da53bdb89317f64"}},"author_association":"OWNER"}}
{"actor":{"display_login":"tiehuis"}}