Allow convert from JSON to a struct and from a struct to JSON
This commit is contained in:
parent
441b50d94b
commit
3d216fbd23
228
src/json.zig
228
src/json.zig
@ -7,6 +7,13 @@ pub const JSONError = error{
|
||||
UnexpectedCharacter,
|
||||
} || std.mem.Allocator.Error;
|
||||
|
||||
pub const JSONConvertError = error{
|
||||
IncorrectArrayLength,
|
||||
IncorrectType,
|
||||
MissingValue,
|
||||
UnsupportedType,
|
||||
};
|
||||
|
||||
pub const JSONType = enum {
|
||||
Object,
|
||||
Array,
|
||||
@ -65,7 +72,7 @@ pub const JSONObject = struct {
|
||||
return self.children.get(name);
|
||||
}
|
||||
|
||||
pub fn print(self: *JSONObject, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
|
||||
pub fn write(self: *JSONObject, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
|
||||
try writer.writeByte('{');
|
||||
|
||||
var iter = self.children.keyIterator();
|
||||
@ -310,7 +317,7 @@ pub const JSONObjectValue = struct {
|
||||
|
||||
fn print(value: *const JSONValue, writer: std.fs.File.Writer, format: JSONFormat, indent_level: usize) std.fs.File.WriteError!void {
|
||||
const self: *const JSONObjectValue = @fieldParentPtr("value", value);
|
||||
try self.object.print(writer, format, indent_level);
|
||||
try self.object.write(writer, format, indent_level);
|
||||
}
|
||||
|
||||
fn toString(value: *const JSONValue, allocator: std.mem.Allocator, format: JSONFormat, indent_level: usize) std.mem.Allocator.Error![]const u8 {
|
||||
@ -494,6 +501,14 @@ pub fn parseFile(allocator: std.mem.Allocator, fileName: []const u8) !*JSONObjec
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
// This will leak memory in it's current state, and if you free the object, the struct may be referencing invalid memory
|
||||
// This is a problem that can be solved, but I'm going to think on it for a bit
|
||||
// pub fn parseFileToStruct(allocator: std.mem.Allocator, fileName: []const u8, T: type) !T {
|
||||
// const obj = try parseFile(allocator, fileName);
|
||||
// errdefer obj.deinit();
|
||||
// return try JSONtoNewStruct(obj, T);
|
||||
// }
|
||||
|
||||
pub fn parseString(allocator: std.mem.Allocator, string: []const u8) !*JSONObject {
|
||||
var parser = try JSONParser.init(allocator, string);
|
||||
return parser.parse();
|
||||
@ -504,7 +519,7 @@ pub fn writeToFile(fileName: []const u8, json: *JSONObject, format: JSONFormat)
|
||||
defer file.close();
|
||||
const writer = file.writer();
|
||||
|
||||
return json.print(writer, format, 0);
|
||||
return json.write(writer, format, 0);
|
||||
}
|
||||
|
||||
const JSONParser = struct {
|
||||
@ -855,6 +870,213 @@ const JSONParser = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn JSONtoNewStruct(object: *JSONObject, T: type) JSONConvertError!T {
|
||||
var result: T = undefined;
|
||||
try JSONtoStruct(object, T, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn JSONtoStruct(object: *JSONObject, T: type, mem: *T) JSONConvertError!void {
|
||||
const result_type_info = @typeInfo(T);
|
||||
if (result_type_info != .Struct) {
|
||||
@compileError("Can only convert JSONObject to struct, not " ++ @typeName(T));
|
||||
}
|
||||
|
||||
const fields_info = result_type_info.Struct.fields;
|
||||
inline for (fields_info) |field| {
|
||||
try convertJSONtoStructField(field.type, &@field(mem.*, field.name), object.children.get(field.name));
|
||||
}
|
||||
}
|
||||
|
||||
fn convertJSONtoStructField(field_type1: type, field: *field_type1, value_or_null: ?*JSONValue) JSONConvertError!void {
|
||||
comptime var field_type = field_type1;
|
||||
comptime var field_type_info = @typeInfo(field_type);
|
||||
|
||||
if (value_or_null == null or value_or_null.?.type == .Null) {
|
||||
if (field_type_info == .Optional) {
|
||||
field.* = null;
|
||||
return;
|
||||
} else {
|
||||
return JSONConvertError.MissingValue;
|
||||
}
|
||||
}
|
||||
|
||||
const value = value_or_null.?;
|
||||
if (field_type_info == .Optional) {
|
||||
field_type = field_type_info.Optional.child;
|
||||
field_type_info = @typeInfo(field_type);
|
||||
}
|
||||
|
||||
switch (field_type_info) {
|
||||
.Int => {
|
||||
if (value.type == .Int) {
|
||||
const int_value = value.getInt() catch @panic("JSON value type does not match what was reported");
|
||||
field.* = int_value;
|
||||
} else {
|
||||
return JSONConvertError.IncorrectType;
|
||||
}
|
||||
},
|
||||
.Float => {
|
||||
if (value.type == .Float) {
|
||||
const float_value = value.getFloat() catch @panic("JSON value type does not match what was reported");
|
||||
field.* = float_value;
|
||||
} else {
|
||||
return JSONConvertError.IncorrectType;
|
||||
}
|
||||
},
|
||||
.Bool => {
|
||||
if (value.type == .Bool) {
|
||||
field.* = value.getBool() catch @panic("JSON value type does not match what was reported");
|
||||
} else {
|
||||
return JSONConvertError.IncorrectType;
|
||||
}
|
||||
},
|
||||
.Pointer => {
|
||||
if (field_type_info.Pointer.size == .Slice) {
|
||||
if (field_type_info.Pointer.child == u8 and value.type == .String) {
|
||||
field.* = value.getString() catch @panic("JSON value type does not match what was reported");
|
||||
} else if (field_type_info.Pointer.child == *JSONValue and value.type == .Array) {
|
||||
field.* = value.getArray() catch @panic("JSON value type does not match what was reported");
|
||||
} else {
|
||||
return JSONConvertError.UnsupportedType;
|
||||
}
|
||||
}
|
||||
},
|
||||
.Array => {
|
||||
if (value.type == .Array) {
|
||||
const array = value.getArray() catch @panic("JSON value type does not match what was reported");
|
||||
if (array.len != field_type_info.Array.len) {
|
||||
return JSONConvertError.IncorrectArrayLength;
|
||||
}
|
||||
|
||||
inline for (0..field_type_info.Array.len) |i| {
|
||||
try convertJSONtoStructField(field_type_info.Array.child, &field.*[i], array[i]);
|
||||
}
|
||||
} else {
|
||||
return JSONConvertError.IncorrectType;
|
||||
}
|
||||
},
|
||||
.Struct => {
|
||||
if (field_type == JSONObject) {
|
||||
if (value.type == .Object) {
|
||||
field.* = value.getObject() catch @panic("JSON value type does not match what was reported");
|
||||
} else {
|
||||
return JSONConvertError.IncorrectType;
|
||||
}
|
||||
} else {
|
||||
if (value.type == .Object) {
|
||||
const subObject = value.getObject() catch @panic("JSON value type does not match what was reported");
|
||||
try JSONtoStruct(subObject, field_type, field);
|
||||
} else {
|
||||
return JSONConvertError.IncorrectType;
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {
|
||||
@compileLog(field_type_info);
|
||||
@compileError(@typeName(field_type) ++ " is not a supported type for converting from JSON");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn structToJSON(allocator: std.mem.Allocator, src: anytype) (JSONConvertError || JSONError)!*JSONObject {
|
||||
const src_type = @TypeOf(src);
|
||||
const type_info = @typeInfo(src_type);
|
||||
if (type_info != .Struct) {
|
||||
@compileError("structToJSON: Expected struct argument, found " ++ @typeName(src_type));
|
||||
}
|
||||
|
||||
var object = try JSONObject.create(allocator);
|
||||
errdefer object.deinit();
|
||||
|
||||
inline for (type_info.Struct.fields) |field| {
|
||||
var field_value = @field(src, field.name);
|
||||
const value = try convertStructFieldtoJSON(allocator, field.type, &field_value);
|
||||
errdefer value.deinit();
|
||||
const name = try allocator.dupe(u8, field.name);
|
||||
errdefer allocator.free(name);
|
||||
try object.children.put(name, value);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
fn convertStructFieldtoJSON(allocator: std.mem.Allocator, field_type: type, value: *field_type) (JSONConvertError || JSONError)!*JSONValue {
|
||||
const field_type_info = @typeInfo(field_type);
|
||||
switch (field_type_info) {
|
||||
.Int => {
|
||||
return try JSONIntValue.create(allocator, value.*);
|
||||
},
|
||||
.Float => {
|
||||
return try JSONFloatValue.create(allocator, value.*);
|
||||
},
|
||||
.Bool => {
|
||||
return try JSONBoolValue.create(allocator, value.*);
|
||||
},
|
||||
.Optional => {
|
||||
if (value.* == null) {
|
||||
return try JSONValue.createNull(allocator);
|
||||
} else {
|
||||
return convertStructFieldtoJSON(allocator, field_type_info.Optional.child, &value.*.?);
|
||||
}
|
||||
},
|
||||
.Struct => {
|
||||
if (field_type == JSONObject) {
|
||||
return try JSONObjectValue.create(allocator, value);
|
||||
} else {
|
||||
const obj = try structToJSON(allocator, value.*);
|
||||
errdefer obj.deinit();
|
||||
return try JSONObjectValue.create(allocator, obj);
|
||||
}
|
||||
},
|
||||
.Array => {
|
||||
var values = std.ArrayList(*JSONValue).init(allocator);
|
||||
errdefer {
|
||||
for (values.items) |val| val.deinit();
|
||||
values.deinit();
|
||||
}
|
||||
|
||||
for (0..field_type_info.Array.len) |i| {
|
||||
const json_value = try convertStructFieldtoJSON(allocator, field_type_info.Array.child, &value.*[i]);
|
||||
errdefer json_value.deinit();
|
||||
try values.append(json_value);
|
||||
}
|
||||
|
||||
const values_slice = try values.toOwnedSlice();
|
||||
errdefer allocator.free(values_slice);
|
||||
return try JSONArrayValue.create(allocator, values_slice);
|
||||
},
|
||||
.Pointer => {
|
||||
if (field_type_info.Pointer.size == .Slice) {
|
||||
if (field_type_info.Pointer.child == u8) {
|
||||
const string = try allocator.dupe(u8, value.*);
|
||||
errdefer allocator.free(string);
|
||||
return try JSONStringValue.create(allocator, string);
|
||||
} else {
|
||||
var values = std.ArrayList(*JSONValue).init(allocator);
|
||||
errdefer {
|
||||
for (values.items) |val| val.deinit();
|
||||
values.deinit();
|
||||
}
|
||||
|
||||
for (0..value.*.len) |i| {
|
||||
const json_value = try convertStructFieldtoJSON(allocator, field_type_info.Pointer.child, &value.*[i]);
|
||||
errdefer json_value.deinit();
|
||||
try values.append(json_value);
|
||||
}
|
||||
|
||||
const values_slice = try values.toOwnedSlice();
|
||||
errdefer allocator.free(values_slice);
|
||||
return try JSONArrayValue.create(allocator, values_slice);
|
||||
}
|
||||
} else {
|
||||
return JSONConvertError.UnsupportedType;
|
||||
}
|
||||
},
|
||||
else => @compileError("Unsupported type: " ++ @typeName(field_type)),
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
39
src/main.zig
39
src/main.zig
@ -1,6 +1,21 @@
|
||||
const std = @import("std");
|
||||
const json = @import("json.zig");
|
||||
|
||||
const TestStruct = struct {
|
||||
hello: []const u8,
|
||||
item2: struct {
|
||||
subitem: isize,
|
||||
otheritem: f64,
|
||||
subobject: struct {
|
||||
subsubitem: [4][]const u8,
|
||||
},
|
||||
},
|
||||
array: [5]isize,
|
||||
online: bool,
|
||||
active: bool,
|
||||
null: ?isize,
|
||||
};
|
||||
|
||||
pub fn main() void {
|
||||
if (std.os.argv.len != 2) {
|
||||
std.debug.print("Usage: {s} [file.json]\n", .{std.os.argv[0]});
|
||||
@ -30,11 +45,33 @@ pub fn main() void {
|
||||
|
||||
const stdout = std.io.getStdOut();
|
||||
const writer = stdout.writer();
|
||||
root.print(writer, .{}, 0) catch {};
|
||||
root.write(writer, .{}, 0) catch {};
|
||||
std.debug.print("\n", .{});
|
||||
|
||||
const json_str = root.toString(allocator, .{}, 0) catch return;
|
||||
std.debug.print("{s}\n", .{json_str});
|
||||
|
||||
var result = json.JSONtoNewStruct(root, TestStruct) catch |err| {
|
||||
std.debug.print("Failed to convert JSON to struct: {any}\n", .{err});
|
||||
return;
|
||||
};
|
||||
std.debug.print("hello: {s}\n", .{result.hello});
|
||||
std.debug.print("subitem: {d}\n", .{result.item2.subitem});
|
||||
std.debug.print("otheritem: {d}\n", .{result.item2.otheritem});
|
||||
std.debug.print("subsubitem: {any}\n", .{result.item2.subobject.subsubitem});
|
||||
std.debug.print("array: {any}\n", .{result.array});
|
||||
std.debug.print("online: {}\n", .{result.online});
|
||||
std.debug.print("active: {}\n", .{result.active});
|
||||
std.debug.print("null: {?}\n", .{result.null});
|
||||
|
||||
result.hello = "potato";
|
||||
result.item2.subitem = -25;
|
||||
|
||||
const back2JSON = json.structToJSON(allocator, result) catch |err| {
|
||||
std.debug.print("Failed to convert struct back to JSON: {any}\n", .{err});
|
||||
return;
|
||||
};
|
||||
back2JSON.write(writer, .{}, 0) catch return;
|
||||
}
|
||||
|
||||
fn printIndent(indent: usize) void {
|
||||
|
Loading…
Reference in New Issue
Block a user