Нормальный json

This commit is contained in:
2026-03-02 17:37:00 +03:00
parent 8ea5d97c2d
commit 37eea6ed89

View File

@@ -1,16 +1,5 @@
const std = @import("std"); const std = @import("std");
const Document = @import("../models/Document.zig"); const Document = @import("../models/Document.zig");
const Object = Document.Object;
const Property = @import("../models/Property.zig").Property;
const PropertyData = @import("../models/Property.zig").Data;
const basic_models = @import("../models/basic_models.zig");
const Point2_f = basic_models.Point2_f;
const Size_f = basic_models.Size_f;
const Scale2_f = basic_models.Scale2_f;
const Radii_f = basic_models.Radii_f;
pub const json_version: u32 = 1;
const JsonError = std.json.Stringify.Error;
pub fn saveToFile(doc: *const Document, path: []const u8) !void { pub fn saveToFile(doc: *const Document, path: []const u8) !void {
var file = try std.fs.cwd().createFile(path, .{ .truncate = true }); var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
@@ -35,331 +24,7 @@ pub fn loadFromFile(allocator: std.mem.Allocator, path: []const u8) !Document {
const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(data); defer allocator.free(data);
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, data, .{ .parse_numbers = true }); const parsed = try std.json.parseFromSlice(Document, allocator, data, .{ .parse_numbers = true });
defer parsed.deinit();
return try parseDocumentValue(allocator, parsed.value); return parsed.value;
}
fn writeDocument(jw: *std.json.Stringify, doc: *const Document) JsonError!void {
try jw.beginObject();
try jw.objectField("version");
try jw.write(json_version);
try jw.objectField("size");
try writeSize(jw, doc.size);
try jw.objectField("next_object_id");
try jw.write(doc.next_object_id);
try jw.objectField("objects");
try writeObjectArray(jw, doc.objects.items);
try jw.endObject();
}
fn writeSize(jw: *std.json.Stringify, size: Size_f) JsonError!void {
try jw.beginObject();
try jw.objectField("w");
try jw.write(size.w);
try jw.objectField("h");
try jw.write(size.h);
try jw.endObject();
}
fn writePoint(jw: *std.json.Stringify, pt: Point2_f) JsonError!void {
try jw.beginObject();
try jw.objectField("x");
try jw.write(pt.x);
try jw.objectField("y");
try jw.write(pt.y);
try jw.endObject();
}
fn writeScale(jw: *std.json.Stringify, scale: Scale2_f) JsonError!void {
try jw.beginObject();
try jw.objectField("scale_x");
try jw.write(scale.scale_x);
try jw.objectField("scale_y");
try jw.write(scale.scale_y);
try jw.endObject();
}
fn writeRadii(jw: *std.json.Stringify, radii: Radii_f) JsonError!void {
try jw.beginObject();
try jw.objectField("x");
try jw.write(radii.x);
try jw.objectField("y");
try jw.write(radii.y);
try jw.endObject();
}
fn writeObjectArray(jw: *std.json.Stringify, objects: []const Object) JsonError!void {
try jw.beginArray();
for (objects) |obj| {
try writeObject(jw, obj);
}
try jw.endArray();
}
fn writeObject(jw: *std.json.Stringify, obj: Object) JsonError!void {
try jw.beginObject();
try jw.objectField("id");
try jw.write(obj.id);
try jw.objectField("shape");
try jw.write(@tagName(obj.shape));
try jw.objectField("properties");
try writeProperties(jw, obj.properties.items);
try jw.objectField("children");
try writeObjectArray(jw, obj.children.items);
try jw.endObject();
}
fn writeProperties(jw: *std.json.Stringify, props: []const Property) JsonError!void {
try jw.beginArray();
for (props) |prop| {
try writeProperty(jw, prop);
}
try jw.endArray();
}
fn writeProperty(jw: *std.json.Stringify, prop: Property) JsonError!void {
try jw.beginObject();
try jw.objectField("tag");
try jw.write(@tagName(prop.data));
try jw.objectField("value");
switch (prop.data) {
.position => |p| try writePoint(jw, p),
.angle => |v| try jw.write(v),
.scale => |s| try writeScale(jw, s),
.visible => |v| try jw.write(v),
.opacity => |v| try jw.write(v),
.locked => |v| try jw.write(v),
.size => |s| try writeSize(jw, s),
.radii => |r| try writeRadii(jw, r),
.end_point => |p| try writePoint(jw, p),
.points => |list| {
try jw.beginArray();
for (list.items) |pt| {
try writePoint(jw, pt);
}
try jw.endArray();
},
.fill_rgba => |v| try jw.write(v),
.stroke_rgba => |v| try jw.write(v),
.thickness => |v| try jw.write(v),
}
try jw.endObject();
}
fn parseDocumentValue(allocator: std.mem.Allocator, value: std.json.Value) !Document {
const obj = try expectObject(value);
const version_val = try getField(obj, "version");
const version = try valueToU32(version_val);
if (version != json_version) return error.UnsupportedVersion;
const size_val = try getField(obj, "size");
const size = try parseSize(size_val);
var document = Document{
.size = size,
.objects = std.ArrayList(Object).empty,
.next_object_id = 1,
};
errdefer document.deinit(allocator);
const objects_val = try getField(obj, "objects");
const objects_arr = try expectArray(objects_val);
for (objects_arr.items) |item| {
const obj_item = try parseObject(allocator, item);
try document.objects.append(allocator, obj_item);
}
if (obj.get("next_object_id")) |next_val| {
const next_id = try valueToU64(next_val);
document.next_object_id = next_id;
} else {
document.next_object_id = computeNextObjectId(document.objects.items);
}
return document;
}
fn parseObject(allocator: std.mem.Allocator, value: std.json.Value) !Object {
const obj = try expectObject(value);
const id = try valueToU64(try getField(obj, "id"));
const shape_str = try valueToString(try getField(obj, "shape"));
const shape = std.meta.stringToEnum(Object.ShapeKind, shape_str) orelse return error.InvalidJson;
var properties = std.ArrayList(Property).empty;
errdefer {
for (properties.items) |*p| p.deinit(allocator);
properties.deinit(allocator);
}
var children = std.ArrayList(Object).empty;
errdefer {
for (children.items) |*c| c.deinit(allocator);
children.deinit(allocator);
}
const props_val = try getField(obj, "properties");
const props_arr = try expectArray(props_val);
for (props_arr.items) |item| {
try properties.append(allocator, try parseProperty(allocator, item));
}
const children_val = try getField(obj, "children");
const children_arr = try expectArray(children_val);
for (children_arr.items) |item| {
try children.append(allocator, try parseObject(allocator, item));
}
return .{
.id = id,
.shape = shape,
.properties = properties,
.children = children,
};
}
fn parseProperty(allocator: std.mem.Allocator, value: std.json.Value) !Property {
const obj = try expectObject(value);
const tag = try valueToString(try getField(obj, "tag"));
const val = try getField(obj, "value");
const data: PropertyData = if (std.mem.eql(u8, tag, "position")) blk: {
break :blk .{ .position = try parsePoint(val) };
} else if (std.mem.eql(u8, tag, "angle")) blk: {
break :blk .{ .angle = try valueToF32(val) };
} else if (std.mem.eql(u8, tag, "scale")) blk: {
break :blk .{ .scale = try parseScale(val) };
} else if (std.mem.eql(u8, tag, "visible")) blk: {
break :blk .{ .visible = try valueToBool(val) };
} else if (std.mem.eql(u8, tag, "opacity")) blk: {
break :blk .{ .opacity = try valueToF32(val) };
} else if (std.mem.eql(u8, tag, "locked")) blk: {
break :blk .{ .locked = try valueToBool(val) };
} else if (std.mem.eql(u8, tag, "size")) blk: {
break :blk .{ .size = try parseSize(val) };
} else if (std.mem.eql(u8, tag, "radii")) blk: {
break :blk .{ .radii = try parseRadii(val) };
} else if (std.mem.eql(u8, tag, "end_point")) blk: {
break :blk .{ .end_point = try parsePoint(val) };
} else if (std.mem.eql(u8, tag, "points")) blk: {
const arr = try expectArray(val);
var list = std.ArrayList(Point2_f).empty;
errdefer list.deinit(allocator);
for (arr.items) |item| {
try list.append(allocator, try parsePoint(item));
}
break :blk .{ .points = list };
} else if (std.mem.eql(u8, tag, "fill_rgba")) blk: {
break :blk .{ .fill_rgba = try valueToU32(val) };
} else if (std.mem.eql(u8, tag, "stroke_rgba")) blk: {
break :blk .{ .stroke_rgba = try valueToU32(val) };
} else if (std.mem.eql(u8, tag, "thickness")) blk: {
break :blk .{ .thickness = try valueToF32(val) };
} else {
return error.InvalidJson;
};
return .{ .data = data };
}
fn parsePoint(value: std.json.Value) !Point2_f {
const obj = try expectObject(value);
const x = try valueToF32(try getField(obj, "x"));
const y = try valueToF32(try getField(obj, "y"));
return .{ .x = x, .y = y };
}
fn parseSize(value: std.json.Value) !Size_f {
const obj = try expectObject(value);
const w = try valueToF32(try getField(obj, "w"));
const h = try valueToF32(try getField(obj, "h"));
return .{ .w = w, .h = h };
}
fn parseScale(value: std.json.Value) !Scale2_f {
const obj = try expectObject(value);
const sx = try valueToF32(try getField(obj, "scale_x"));
const sy = try valueToF32(try getField(obj, "scale_y"));
return .{ .scale_x = sx, .scale_y = sy };
}
fn parseRadii(value: std.json.Value) !Radii_f {
const obj = try expectObject(value);
const x = try valueToF32(try getField(obj, "x"));
const y = try valueToF32(try getField(obj, "y"));
return .{ .x = x, .y = y };
}
fn expectObject(value: std.json.Value) !std.json.ObjectMap {
return switch (value) {
.object => |o| o,
else => error.InvalidJson,
};
}
fn expectArray(value: std.json.Value) !std.json.Array {
return switch (value) {
.array => |a| a,
else => error.InvalidJson,
};
}
fn getField(obj: std.json.ObjectMap, key: []const u8) !std.json.Value {
return obj.get(key) orelse error.InvalidJson;
}
fn valueToString(value: std.json.Value) ![]const u8 {
return switch (value) {
.string => |s| s,
else => error.InvalidJson,
};
}
fn valueToBool(value: std.json.Value) !bool {
return switch (value) {
.bool => |b| b,
else => error.InvalidJson,
};
}
fn valueToF32(value: std.json.Value) !f32 {
return switch (value) {
.float => |f| @floatCast(f),
.integer => |i| @floatFromInt(i),
else => error.InvalidJson,
};
}
fn valueToU32(value: std.json.Value) !u32 {
const v = switch (value) {
.integer => |i| i,
else => return error.InvalidJson,
};
if (v < 0 or v > std.math.maxInt(u32)) return error.InvalidJson;
return @intCast(v);
}
fn valueToU64(value: std.json.Value) !u64 {
const v = switch (value) {
.integer => |i| i,
else => return error.InvalidJson,
};
if (v < 0) return error.InvalidJson;
return @intCast(v);
}
fn computeNextObjectId(objects: []const Object) u64 {
var max_id: u64 = 0;
for (objects) |obj| {
max_id = @max(max_id, maxIdInObject(obj));
}
return max_id + 1;
}
fn maxIdInObject(obj: Object) u64 {
var max_id = obj.id;
for (obj.children.items) |child| {
max_id = @max(max_id, maxIdInObject(child));
}
return max_id;
} }