ZigJSON/src/json.zig

895 lines
26 KiB
Zig
Raw Normal View History

2024-08-01 17:41:50 +00:00
const std = @import("std");
pub const JSONError = error{
IncorrectType,
UnexpectedEOF,
UnexpectedCharacter,
} || std.mem.Allocator.Error;
pub const JSONType = enum {
Object,
Array,
String,
Int,
Float,
Bool,
Null,
};
pub const JSONObject = struct {
allocator: std.mem.Allocator,
children: MapType,
const MapType = std.StringHashMap(*JSONValue);
pub fn get(self: *JSONObject, name: []const u8) ?*JSONValue {
return self.children.get(name);
}
fn init(allocator: std.mem.Allocator) JSONError!*JSONObject {
var self = try allocator.create(JSONObject);
self.allocator = allocator;
self.children = MapType.init(allocator);
return self;
}
pub fn deinit(self: *JSONObject) void {
var iter = self.children.valueIterator();
while (iter.next()) |child| {
child.*.deinit();
}
var names = self.children.keyIterator();
while (names.next()) |name| {
self.allocator.free(name.*);
}
self.children.deinit();
self.allocator.destroy(self);
}
};
pub const JSONValue = struct {
allocator: std.mem.Allocator,
type: JSONType,
vtable: VTable,
const VTable = struct {
getString: *const fn (self: *const JSONValue) JSONError![]const u8 = incorrectValue([]const u8),
getArray: *const fn (self: *const JSONValue) JSONError![]*JSONValue = incorrectValue([]*JSONValue),
getObject: *const fn (self: *const JSONValue) JSONError!*JSONObject = incorrectValue(*JSONObject),
getInt: *const fn (self: *const JSONValue) JSONError!isize = incorrectValue(isize),
getFloat: *const fn (self: *const JSONValue) JSONError!f64 = incorrectValue(f64),
getBool: *const fn (self: *const JSONValue) JSONError!bool = incorrectValue(bool),
deinit: *const fn (self: *JSONValue) void,
};
fn incorrectValue(comptime T: type) *const fn (*const JSONValue) JSONError!T {
return struct {
fn func(_: *const JSONValue) JSONError!T {
return JSONError.IncorrectType;
}
}.func;
}
pub fn getBool(self: *const JSONValue) JSONError!bool {
return self.vtable.getBool(self);
}
pub fn getInt(self: *const JSONValue) JSONError!isize {
return self.vtable.getInt(self);
}
pub fn getFloat(self: *const JSONValue) JSONError!f64 {
return self.vtable.getFloat(self);
}
pub fn getString(self: *const JSONValue) JSONError![]const u8 {
return self.vtable.getString(self);
}
pub fn getArray(self: *const JSONValue) JSONError![]*JSONValue {
return self.vtable.getArray(self);
}
pub fn getObject(self: *const JSONValue) JSONError!*JSONObject {
return self.vtable.getObject(self);
}
pub fn deinit(self: *JSONValue) void {
self.vtable.deinit(self);
}
fn createNull(allocator: std.mem.Allocator) JSONError!*JSONValue {
var self = try allocator.create(JSONValue);
self.type = .Null;
self.vtable = .{ .deinit = basicDeinit };
self.allocator = allocator;
return self;
}
fn basicDeinit(self: *JSONValue) void {
self.allocator.destroy(self);
}
};
const JSONArrayValue = struct {
value: JSONValue,
array: []*JSONValue,
fn init(allocator: std.mem.Allocator, array: []*JSONValue) !*JSONValue {
var self = try allocator.create(JSONArrayValue);
self.array = array;
self.value = .{
.type = .Array,
.vtable = .{ .getArray = getValue, .deinit = deinit },
.allocator = allocator,
};
return &self.value;
}
fn getValue(value: *const JSONValue) JSONError![]*JSONValue {
const self: *const JSONArrayValue = @fieldParentPtr("value", value);
return self.array;
}
fn deinit(value: *JSONValue) void {
var self: *JSONArrayValue = @fieldParentPtr("value", value);
for (self.array) |val| {
val.deinit();
}
self.value.allocator.free(self.array);
self.value.allocator.destroy(self);
}
};
const JSONObjectValue = struct {
value: JSONValue,
object: *JSONObject,
fn init(allocator: std.mem.Allocator, object: *JSONObject) !*JSONValue {
var self = try allocator.create(JSONObjectValue);
self.object = object;
self.value = .{
.type = .Object,
.vtable = .{ .getObject = getValue, .deinit = deinit },
.allocator = allocator,
};
return &self.value;
}
fn getValue(value: *const JSONValue) JSONError!*JSONObject {
const self: *const JSONObjectValue = @fieldParentPtr("value", value);
return self.object;
}
fn deinit(value: *JSONValue) void {
var self: *const JSONObjectValue = @fieldParentPtr("value", value);
self.object.deinit();
self.value.allocator.destroy(self);
}
};
const JSONIntValue = struct {
value: JSONValue,
int: isize,
fn init(allocator: std.mem.Allocator, value: isize) !*JSONValue {
var self = try allocator.create(JSONIntValue);
self.int = value;
self.value = .{
.type = .Int,
.vtable = .{ .getInt = getValue, .deinit = deinit },
.allocator = allocator,
};
return &self.value;
}
fn getValue(value: *const JSONValue) JSONError!isize {
const self: *const JSONIntValue = @fieldParentPtr("value", value);
return self.int;
}
fn deinit(value: *JSONValue) void {
var self: *JSONIntValue = @fieldParentPtr("value", value);
self.value.allocator.destroy(self);
}
};
const JSONFloatValue = struct {
value: JSONValue,
float: f64,
fn init(allocator: std.mem.Allocator, value: f64) !*JSONValue {
var self = try allocator.create(JSONFloatValue);
self.float = value;
self.value = .{
.type = .Float,
.vtable = .{ .getFloat = getValue, .deinit = deinit },
.allocator = allocator,
};
return &self.value;
}
fn getValue(value: *const JSONValue) JSONError!f64 {
const self: *const JSONFloatValue = @fieldParentPtr("value", value);
return self.float;
}
fn deinit(value: *JSONValue) void {
var self: *JSONIntValue = @fieldParentPtr("value", value);
self.value.allocator.destroy(self);
}
};
const JSONBoolValue = struct {
value: JSONValue,
boolean: bool,
fn init(allocator: std.mem.Allocator, value: bool) !*JSONValue {
var self = try allocator.create(JSONBoolValue);
self.boolean = value;
self.value = .{
.type = .Bool,
.vtable = .{ .getBool = getValue, .deinit = deinit },
.allocator = allocator,
};
return &self.value;
}
fn getValue(value: *const JSONValue) JSONError!bool {
const self: *const JSONBoolValue = @fieldParentPtr("value", value);
return self.boolean;
}
fn deinit(value: *JSONValue) void {
var self: *JSONBoolValue = @fieldParentPtr("value", value);
self.value.allocator.destroy(self);
}
};
pub const JSONStringValue = struct {
value: JSONValue,
string: []const u8,
pub fn init(allocator: std.mem.Allocator, string: []const u8) !*JSONValue {
var self = try allocator.create(JSONStringValue);
self.string = string;
self.value = .{
.type = .String,
.vtable = .{ .getString = getValue, .deinit = deinit },
.allocator = allocator,
};
return &self.value;
}
fn getValue(value: *const JSONValue) JSONError![]const u8 {
const self: *const JSONStringValue = @fieldParentPtr("value", value);
return self.string;
}
fn deinit(value: *JSONValue) void {
var self: *JSONStringValue = @fieldParentPtr("value", value);
self.value.allocator.free(self.string);
self.value.allocator.destroy(self);
}
};
pub fn parseFile(allocator: std.mem.Allocator, fileName: []const u8) !*JSONObject {
const fileContents = blk: {
var file = try std.fs.cwd().openFile(fileName, .{ .mode = .read_only });
defer file.close();
break :blk try file.readToEndAlloc(allocator, 64 * 1024 * 1024);
};
defer allocator.free(fileContents);
var parser = try JSONParser.init(allocator, fileContents);
return parser.parse();
}
pub fn parseString(allocator: std.mem.Allocator, string: []const u8) !*JSONObject {
var parser = try JSONParser.init(allocator, string);
return parser.parse();
}
const JSONParser = struct {
allocator: std.mem.Allocator,
iter: std.unicode.Utf8Iterator,
current: u21 = 0,
end: bool = false,
fn init(allocator: std.mem.Allocator, input: []const u8) !JSONParser {
return JSONParser{
.allocator = allocator,
.iter = (try std.unicode.Utf8View.init(input)).iterator(),
};
}
fn next(self: *JSONParser) void {
if (self.iter.nextCodepoint()) |code_point| {
self.current = code_point;
} else {
self.end = true;
}
}
fn consume(self: *JSONParser) u21 {
const code_point = self.current;
self.next();
return code_point;
}
fn skipWhitespace(self: *JSONParser) JSONError!void {
while (!self.end) {
switch (self.current) {
' ', '\t', '\n', '\r' => {},
else => return,
}
self.next();
}
return JSONError.UnexpectedEOF;
}
fn expectNext(self: *JSONParser, code_point: u21) JSONError!void {
//std.debug.print("Expecting {u} ({X}) to be {u} ({X})\n", .{ self.current, self.current, code_point, code_point });
if (self.current != code_point or self.end) {
return JSONError.UnexpectedCharacter;
}
self.next();
}
fn parse(self: *JSONParser) JSONError!*JSONObject {
self.next();
try self.skipWhitespace();
return self.parseObject();
}
fn parseObject(self: *JSONParser) JSONError!*JSONObject {
const obj = try JSONObject.init(self.allocator);
errdefer obj.deinit();
try self.expectNext('{');
try self.skipWhitespace();
if (self.current != '}') {
while (true) {
try self.skipWhitespace();
const valueName = try self.parseString();
errdefer self.allocator.free(valueName);
try self.skipWhitespace();
//std.debug.print("Value name: {s}\n", .{valueName});
try self.expectNext(':');
const value = try self.parseValue();
errdefer value.deinit();
try obj.children.put(valueName, value);
// Do-while would have been better here
if (self.current != ',') {
break;
}
self.next();
}
}
try self.expectNext('}');
return obj;
}
fn parseArray(self: *JSONParser) JSONError![]*JSONValue {
var array = std.ArrayList(*JSONValue).init(self.allocator);
errdefer {
for (array.items) |value| {
value.deinit();
}
array.deinit();
}
try self.expectNext('[');
try self.skipWhitespace();
if (self.current != ']') {
while (true) {
try array.append(try self.parseValue());
if (self.current != ',') {
break;
}
self.next();
}
}
try self.expectNext(']');
return array.toOwnedSlice();
}
fn parseValue(self: *JSONParser) JSONError!*JSONValue {
try self.skipWhitespace();
var value: *JSONValue = undefined;
switch (self.current) {
'"' => {
//std.debug.print("Parsing string\n", .{});
const str = try self.parseString();
value = try JSONStringValue.init(self.allocator, str);
},
'-', '0'...'9' => {
//std.debug.print("Parsing number\n", .{});
value = try self.parseNumberValue();
},
'{' => {
//std.debug.print("Parsing object\n", .{});
const obj = try self.parseObject();
value = try JSONObjectValue.init(self.allocator, obj);
},
'[' => {
//std.debug.print("Parsing array\n", .{});
const arr = try self.parseArray();
value = try JSONArrayValue.init(self.allocator, arr);
},
't', 'f' => {
//std.debug.print("Parsing bool\n", .{});
const b = try self.parseBool();
value = try JSONBoolValue.init(self.allocator, b);
},
'n' => {
//std.debug.print("Parsing null\n", .{});
self.next();
try self.expectNext('u');
try self.expectNext('l');
try self.expectNext('l');
value = try JSONValue.createNull(self.allocator);
},
else => {
std.debug.print("Unexpected character: {u}\n", .{self.current});
return JSONError.UnexpectedCharacter;
},
}
self.skipWhitespace() catch |err| {
value.deinit();
return err;
};
return value;
}
fn parseBool(self: *JSONParser) JSONError!bool {
var b = false;
switch (self.current) {
't' => {
self.next();
try self.expectNext('r');
try self.expectNext('u');
try self.expectNext('e');
b = true;
},
'f' => {
self.next();
try self.expectNext('a');
try self.expectNext('l');
try self.expectNext('s');
try self.expectNext('e');
b = false;
},
else => return JSONError.UnexpectedCharacter,
}
return b;
}
fn parseString(self: *JSONParser) JSONError![]const u8 {
var string = std.ArrayList(u8).init(self.allocator);
defer string.deinit();
try self.expectNext('"');
while (self.current != '"' and !self.end) {
switch (self.consume()) {
'\\' => {
if (self.end) return JSONError.UnexpectedEOF;
const code_point = try self.escapedCharacter();
var utf8: [4]u8 = undefined;
const len = std.unicode.utf8Encode(code_point, &utf8) catch return JSONError.UnexpectedCharacter;
try string.appendSlice(utf8[0..len]);
},
0...std.ascii.control_code.us, std.ascii.control_code.del => return JSONError.UnexpectedCharacter,
else => |c| {
var buf: [4]u8 = undefined;
const len = std.unicode.utf8Encode(c, &buf) catch return JSONError.UnexpectedCharacter;
try string.appendSlice(buf[0..len]);
},
}
}
try self.expectNext('"');
return string.toOwnedSlice();
}
fn escapedCharacter(self: *JSONParser) JSONError!u21 {
return switch (self.consume()) {
'"' => '"',
'\\' => '\\',
'/' => '/',
'b' => '\x08',
'f' => '\x0C',
'n' => '\n',
'r' => '\r',
't' => '\t',
'u' => {
// I hate this
if (self.end) return JSONError.UnexpectedEOF;
const c1 = self.consume();
var b1: [1]u8 = undefined;
_ = std.unicode.utf8Encode(c1, &b1) catch return JSONError.UnexpectedCharacter;
if (self.end) return JSONError.UnexpectedEOF;
const c2 = self.consume();
var b2: [1]u8 = undefined;
_ = std.unicode.utf8Encode(c2, &b2) catch return JSONError.UnexpectedCharacter;
if (self.end) return JSONError.UnexpectedEOF;
const c3 = self.consume();
var b3: [1]u8 = undefined;
_ = std.unicode.utf8Encode(c3, &b3) catch return JSONError.UnexpectedCharacter;
if (self.end) return JSONError.UnexpectedEOF;
const c4 = self.consume();
var b4: [1]u8 = undefined;
_ = std.unicode.utf8Encode(c4, &b4) catch return JSONError.UnexpectedCharacter;
return std.fmt.parseInt(u21, &[4]u8{ b1[0], b2[0], b3[0], b4[0] }, 16) catch JSONError.UnexpectedCharacter;
},
else => JSONError.UnexpectedCharacter,
};
}
fn parseNumberValue(self: *JSONParser) JSONError!*JSONValue {
var buf = std.ArrayList(u8).init(self.allocator);
defer buf.deinit();
var float = false;
var exp: isize = 0;
if (self.current == '-') {
self.next();
try buf.append('-');
}
if (self.end) {
return JSONError.UnexpectedEOF;
}
if (self.current != '0') {
while (!self.end) {
switch (self.current) {
'0'...'9' => |c| {
try buf.append(@intCast(c));
},
else => break,
}
self.next();
}
if (self.current == '.') {
try buf.append('.');
float = true;
self.next();
}
} else {
try self.expectNext('.');
try buf.append('.');
float = true;
}
if (float) {
while (!self.end) {
switch (self.current) {
'0'...'9' => |c| {
try buf.append(@intCast(c));
},
else => break,
}
self.next();
}
}
if (self.current == 'e' or self.current == 'E') {
self.next();
var expBuf = std.ArrayList(u8).init(self.allocator);
defer expBuf.deinit();
while (!self.end) {
switch (self.current) {
'-' => try expBuf.append('-'),
'0'...'9' => |c| try expBuf.append(@intCast(c)),
else => break,
}
self.next();
}
if (expBuf.items.len == 0) {
return JSONError.UnexpectedCharacter;
}
exp = std.fmt.parseInt(isize, expBuf.items, 10) catch return JSONError.UnexpectedCharacter;
}
if (float or exp != 0) {
var value: f64 = std.fmt.parseFloat(f64, buf.items) catch unreachable;
if (exp != 0) {
value *= std.math.pow(f64, 10.0, @floatFromInt(exp));
}
return try JSONFloatValue.init(self.allocator, value);
} else {
const value: isize = std.fmt.parseInt(isize, buf.items, 10) catch unreachable;
return try JSONIntValue.init(self.allocator, value);
}
}
};
test "types" {
var root = try parseString(std.testing.allocator, "{\"string\": \"abc\", \"int\": 12, \"float\": 3.4, \"array\": [], \"object\": {}, \"null\": null, \"bool\": true}");
defer root.deinit();
if (root.get("string")) |value| {
try std.testing.expect(value.type == .String);
} else {
return error.FailedToParse;
}
if (root.get("int")) |value| {
try std.testing.expect(value.type == .Int);
} else {
return error.FailedToParse;
}
if (root.get("float")) |value| {
try std.testing.expect(value.type == .Float);
} else {
return error.FailedToParse;
}
if (root.get("array")) |value| {
try std.testing.expect(value.type == .Array);
} else {
return error.FailedToParse;
}
if (root.get("object")) |value| {
try std.testing.expect(value.type == .Object);
} else {
return error.FailedToParse;
}
if (root.get("null")) |value| {
try std.testing.expect(value.type == .Null);
} else {
return error.FailedToParse;
}
if (root.get("bool")) |value| {
try std.testing.expect(value.type == .Bool);
} else {
return error.FailedToParse;
}
}
test "ints" {
const root = try parseString(std.testing.allocator, "{\"one\": 1, \"two\": 2, \"three\": 3, \"fifteen\": 15, \"negative\": -15}");
defer root.deinit();
if (root.get("one")) |value| {
try std.testing.expect(value.type == .Int);
try std.testing.expect(try value.getInt() == 1);
} else {
return error.FailedToParse;
}
if (root.get("two")) |value| {
try std.testing.expect(value.type == .Int);
try std.testing.expect(try value.getInt() == 2);
} else {
return error.FailedToParse;
}
if (root.get("three")) |value| {
try std.testing.expect(value.type == .Int);
try std.testing.expect(try value.getInt() == 3);
} else {
return error.FailedToParse;
}
if (root.get("fifteen")) |value| {
try std.testing.expect(value.type == .Int);
try std.testing.expect(try value.getInt() == 15);
} else {
return error.FailedToParse;
}
if (root.get("negative")) |value| {
try std.testing.expect(value.type == .Int);
try std.testing.expect(try value.getInt() == -15);
} else {
return error.FailedToParse;
}
}
test "floats" {
const root = try parseString(std.testing.allocator, "{\"one\": 1.2, \"ten\": 1e1, \"three\": -3.4, \"four\": 3.4e12, \"five\": 1e-2}");
defer root.deinit();
if (root.get("one")) |value| {
try std.testing.expect(value.type == .Float);
try std.testing.expect(try value.getFloat() - 1.2 < 0.1);
} else {
return error.FailedToParse;
}
if (root.get("ten")) |value| {
try std.testing.expect(value.type == .Float);
try std.testing.expect(try value.getFloat() - 10 < 0.1);
} else {
return error.FailedToParse;
}
if (root.get("three")) |value| {
try std.testing.expect(value.type == .Float);
try std.testing.expect(try value.getFloat() - -3.4 < 0.1);
} else {
return error.FailedToParse;
}
if (root.get("four")) |value| {
try std.testing.expect(value.type == .Float);
try std.testing.expect(try value.getFloat() - 3400000000000 < 0.1);
} else {
return error.FailedToParse;
}
if (root.get("five")) |value| {
try std.testing.expect(value.type == .Float);
try std.testing.expect(try value.getFloat() - 0.01 < 0.001);
} else {
return error.FailedToParse;
}
}
test "bool" {
const root = try parseString(std.testing.allocator, "{\"true\": true, \"false\": false}");
defer root.deinit();
if (root.get("true")) |value| {
try std.testing.expect(value.type == .Bool);
try std.testing.expect(try value.getBool());
} else {
return error.FailedToParse;
}
if (root.get("false")) |value| {
try std.testing.expect(value.type == .Bool);
try std.testing.expect(!try value.getBool());
} else {
return error.FailedToParse;
}
}
test "string" {
const root = try parseString(std.testing.allocator, "{\"one\": \"abc 123\\r\\t\\n\\u2611 \\\" \\\\ \\/\"}");
defer root.deinit();
if (root.get("one")) |value| {
try std.testing.expect(value.type == .String);
try std.testing.expectEqualStrings("abc 123\r\t\n\" \\ /", try value.getString());
} else {
return error.FailedToParse;
}
}
test "null" {
const root = try parseString(std.testing.allocator, "{\"null\": null}");
defer root.deinit();
if (root.get("null")) |value| {
try std.testing.expect(value.type == .Null);
} else {
return error.FailedToParse;
}
}
test "array" {
const root = try parseString(std.testing.allocator, "{\"empty\": [], \"two\": [1, 2], \"three\": [ 3.1, 3.2, 3.3 ]}");
defer root.deinit();
if (root.get("empty")) |value| {
try std.testing.expect(value.type == .Array);
try std.testing.expect((try value.getArray()).len == 0);
} else {
return error.FailedToParse;
}
if (root.get("two")) |value| {
try std.testing.expect(value.type == .Array);
const arr = try value.getArray();
try std.testing.expect(arr.len == 2);
try std.testing.expect(arr[0].type == .Int);
try std.testing.expect(try arr[0].getInt() == 1);
try std.testing.expect(arr[1].type == .Int);
try std.testing.expect(try arr[1].getInt() == 2);
} else {
return error.FailedToParse;
}
if (root.get("three")) |value| {
try std.testing.expect(value.type == .Array);
const arr = try value.getArray();
try std.testing.expect(arr.len == 3);
try std.testing.expect(arr[0].type == .Float);
try std.testing.expect(try arr[0].getFloat() - 3.1 < 0.1);
try std.testing.expect(arr[1].type == .Float);
try std.testing.expect(try arr[1].getFloat() - 3.2 < 0.1);
try std.testing.expect(arr[2].type == .Float);
try std.testing.expect(try arr[2].getFloat() - 3.3 < 0.1);
} else {
return error.FailedToParse;
}
}
test "objects" {
const root = try parseString(std.testing.allocator, "{\"empty\": {}, \"two\": {\"abc\": 1}}");
defer root.deinit();
if (root.get("empty")) |value| {
try std.testing.expect(value.type == .Object);
const obj = try value.getObject();
try std.testing.expect(obj.children.count() == 0);
} else {
return error.FailedToParse;
}
if (root.get("two")) |value| {
try std.testing.expect(value.type == .Object);
const obj = try value.getObject();
try std.testing.expect(obj.children.count() == 1);
if (obj.get("abc")) |value2| {
try std.testing.expect(value2.type == .Int);
try std.testing.expect(try value2.getInt() == 1);
} else {
return error.FailedToParse;
}
} else {
return error.FailedToParse;
}
}
test "file parsing" {
const root = try parseFile(std.testing.allocator, "test/test.json");
root.deinit();
}