DoxigAlpha

write

Renders the given Zig value as JSON.

Supported types:

  • Zig bool -> JSON true or false.
  • Zig ?T -> null or the rendering of T.
  • Zig i32, u64, etc. -> JSON number or string.
    • When option emit_nonportable_numbers_as_strings is true, if the value is outside the range +-1<<53 (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.
  • Zig floats -> JSON number or string.
    • If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number.
    • TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00".
  • Zig []const u8, []u8, *[N]u8, @Vector(N, u8), and similar -> JSON string.
    • See Options.emit_strings_as_arrays.
    • If the content is not valid UTF-8, rendered as an array of numbers instead.
  • Zig []T, [N]T, *[N]T, @Vector(N, T), and similar -> JSON array of the rendering of each item.
  • Zig tuple -> JSON array of the rendering of each item.
  • Zig struct -> JSON object with each field in declaration order.
    • If the struct declares a method pub fn jsonStringify(self: *@This(), jw: anytype) !void, it is called to do the serialization instead of the default behavior. The given jw is a pointer to this Stringify. See std.json.Value for an example.
    • See Options.emit_null_optional_fields.
  • Zig union(enum) -> JSON object with one field named for the active tag and a value representing the payload.
    • If the payload is void, then the emitted value is {}.
    • If the union declares a method pub fn jsonStringify(self: *@This(), jw: anytype) !void, it is called to do the serialization instead of the default behavior. The given jw is a pointer to this Stringify.
  • Zig enum -> JSON string naming the active tag.
    • If the enum declares a method pub fn jsonStringify(self: *@This(), jw: anytype) !void, it is called to do the serialization instead of the default behavior. The given jw is a pointer to this Stringify.
    • If the enum is non-exhaustive, unnamed values are rendered as integers.
  • Zig untyped enum literal -> JSON string naming the active tag.
  • Zig error -> JSON string naming the error.
  • Zig *T -> the rendering of T. Note there is no guard against circular-reference infinite recursion.

See also alternative functions print and beginWriteRaw. For writing object field names, use objectField instead.

Function parameters

Parameters

#
self:*Stringify
v:anytype

Type definitions in this namespace

Types

#

Functions in this namespace

Functions

#
print
An alternative to calling `write` that formats a value with `std.fmt`.
beginWriteRaw
An alternative to calling `write` that allows you to write directly to the `.writer` field, e.g.
endWriteRaw
See `beginWriteRaw`.
objectField
See `Stringify` for when to call this method.
objectFieldRaw
See `Stringify` for when to call this method.
beginObjectFieldRaw
In the rare case that you need to write very long object field names,
endObjectFieldRaw
See `beginObjectFieldRaw`.
write
Renders the given Zig value as JSON.
value
Writes the given value to the `Writer` writer.
valueAlloc
Calls `value` and stores the result in dynamically allocated memory instead
encodeJsonString
Write `string` to `writer` as a JSON encoded string.
encodeJsonStringChars
Write `chars` to `writer` as JSON encoded string characters.

Error sets in this namespace

Error Sets

#

Source

Implementation

#
pub fn write(self: *Stringify, v: anytype) Error!void {
    if (build_mode_has_safety) assert(self.raw_streaming_mode == .none);
    const T = @TypeOf(v);
    switch (@typeInfo(T)) {
        .int => {
            try self.valueStart();
            if (self.options.emit_nonportable_numbers_as_strings and
                (v <= -(1 << 53) or v >= (1 << 53)))
            {
                try self.writer.print("\"{}\"", .{v});
            } else {
                try self.writer.print("{}", .{v});
            }
            self.valueDone();
            return;
        },
        .comptime_int => {
            return self.write(@as(std.math.IntFittingRange(v, v), v));
        },
        .float, .comptime_float => {
            if (@as(f64, @floatCast(v)) == v) {
                try self.valueStart();
                try self.writer.print("{}", .{@as(f64, @floatCast(v))});
                self.valueDone();
                return;
            }
            try self.valueStart();
            try self.writer.print("\"{}\"", .{v});
            self.valueDone();
            return;
        },

        .bool => {
            try self.valueStart();
            try self.writer.writeAll(if (v) "true" else "false");
            self.valueDone();
            return;
        },
        .null => {
            try self.valueStart();
            try self.writer.writeAll("null");
            self.valueDone();
            return;
        },
        .optional => {
            if (v) |payload| {
                return try self.write(payload);
            } else {
                return try self.write(null);
            }
        },
        .@"enum" => |enum_info| {
            if (std.meta.hasFn(T, "jsonStringify")) {
                return v.jsonStringify(self);
            }

            if (!enum_info.is_exhaustive) {
                inline for (enum_info.fields) |field| {
                    if (v == @field(T, field.name)) {
                        break;
                    }
                } else {
                    return self.write(@intFromEnum(v));
                }
            }

            return self.stringValue(@tagName(v));
        },
        .enum_literal => {
            return self.stringValue(@tagName(v));
        },
        .@"union" => {
            if (std.meta.hasFn(T, "jsonStringify")) {
                return v.jsonStringify(self);
            }

            const info = @typeInfo(T).@"union";
            if (info.tag_type) |UnionTagType| {
                try self.beginObject();
                inline for (info.fields) |u_field| {
                    if (v == @field(UnionTagType, u_field.name)) {
                        try self.objectField(u_field.name);
                        if (u_field.type == void) {
                            // void v is {}
                            try self.beginObject();
                            try self.endObject();
                        } else {
                            try self.write(@field(v, u_field.name));
                        }
                        break;
                    }
                } else {
                    unreachable; // No active tag?
                }
                try self.endObject();
                return;
            } else {
                @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
            }
        },
        .@"struct" => |S| {
            if (std.meta.hasFn(T, "jsonStringify")) {
                return v.jsonStringify(self);
            }

            if (S.is_tuple) {
                try self.beginArray();
            } else {
                try self.beginObject();
            }
            inline for (S.fields) |Field| {
                // don't include void fields
                if (Field.type == void) continue;

                var emit_field = true;

                // don't include optional fields that are null when emit_null_optional_fields is set to false
                if (@typeInfo(Field.type) == .optional) {
                    if (self.options.emit_null_optional_fields == false) {
                        if (@field(v, Field.name) == null) {
                            emit_field = false;
                        }
                    }
                }

                if (emit_field) {
                    if (!S.is_tuple) {
                        try self.objectField(Field.name);
                    }
                    try self.write(@field(v, Field.name));
                }
            }
            if (S.is_tuple) {
                try self.endArray();
            } else {
                try self.endObject();
            }
            return;
        },
        .error_set => return self.stringValue(@errorName(v)),
        .pointer => |ptr_info| switch (ptr_info.size) {
            .one => switch (@typeInfo(ptr_info.child)) {
                .array => {
                    // Coerce `*[N]T` to `[]const T`.
                    const Slice = []const std.meta.Elem(ptr_info.child);
                    return self.write(@as(Slice, v));
                },
                else => {
                    return self.write(v.*);
                },
            },
            .many, .slice => {
                if (ptr_info.size == .many and ptr_info.sentinel() == null)
                    @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel");
                const slice = if (ptr_info.size == .many) std.mem.span(v) else v;

                if (ptr_info.child == u8) {
                    // This is a []const u8, or some similar Zig string.
                    if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) {
                        return self.stringValue(slice);
                    }
                }

                try self.beginArray();
                for (slice) |x| {
                    try self.write(x);
                }
                try self.endArray();
                return;
            },
            else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
        },
        .array => {
            // Coerce `[N]T` to `*const [N]T` (and then to `[]const T`).
            return self.write(&v);
        },
        .vector => |info| {
            const array: [info.len]info.child = v;
            return self.write(&array);
        },
        else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
    }
    unreachable;
}