Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/src/writing.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ JSON.json(io::IO, x) -> IO

# Serialize to a file
JSON.json(file_name::String, x) -> String

# Serialize to a byte vector
JSON.json(Vector{UInt8}, x) -> Vector{UInt8}
```

The [`JSON.json`](@ref) function accepts a wide range of Julia types and transforms them into their JSON representation by knowing how to serialize a core set of types:
Expand Down
25 changes: 19 additions & 6 deletions src/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,12 @@ StructUtils.lowerkey(::JSONStyle, x) = throw(ArgumentError("No key representatio
JSON.json(x) -> String
JSON.json(io, x)
JSON.json(file_name, x)
JSON.json(Vector{UInt8}, x) -> Vector{UInt8}

Serialize `x` to JSON format. The 1st method takes just the object and returns a `String`.
In the 2nd method, `io` is an `IO` object, and the JSON output will be written to it.
For the 3rd method, `file_name` is a `String`, a file will be opened and the JSON output will be written to it.
The 4th method returns the UTF-8 encoded JSON output as a regular `Vector{UInt8}`.

All methods accept the following keyword arguments:

Expand Down Expand Up @@ -462,7 +464,7 @@ All methods accept the following keyword arguments:

- `bufsize::Int=2^22`: Buffer size in bytes for IO operations. When writing to IO, the buffer will be flushed
to the IO stream once it reaches this size. This helps control memory usage during large write operations.
Default is 4MB (2^22 bytes). This parameter is ignored when returning a String.
Default is 4MB (2^22 bytes). This parameter is ignored when returning a `String` or `Vector{UInt8}`.

- `style::JSONStyle=JSONWriteStyle()`: Custom style object that controls serialization behavior. This allows customizing
certain aspects of serialization, like defining a custom `lower` method for a non-owned type. Like `struct MyStyle <: JSONStyle end`,
Expand Down Expand Up @@ -606,11 +608,16 @@ float_precision_check(fs, fp) = (fs == :shortest || fp > 0) || float_precision_t
_jsonlines_pretty_check(jsonlines, pretty) = jsonlines && pretty !== false && !iszero(pretty) && _jsonlines_pretty_throw()
@noinline _root_omit_throw() = throw(ArgumentError("JSON.Omit() is only valid inside arrays or objects"))

function json(io::IO, x::T; pretty::Union{Integer,Bool}=false, kw...) where {T}
function writeoptions(pretty::Union{Integer,Bool}, kw)
opts = WriteOptions(; pretty=pretty === true ? 2 : Int(pretty), kw...)
_jsonlines_pretty_check(opts.jsonlines, opts.pretty)
float_style_check(opts.float_style)
float_precision_check(opts.float_style, opts.float_precision)
return opts
end

function json(io::IO, x::T; pretty::Union{Integer,Bool}=false, kw...) where {T}
opts = writeoptions(pretty, kw)
y = StructUtils.lower(opts.style, x)
# Use smaller initial buffer size, limited by bufsize
initial_size = min(sizeguess(y), opts.bufsize)
Expand All @@ -630,16 +637,22 @@ else
end

function json(x; pretty::Union{Integer,Bool}=false, kw...)
opts = WriteOptions(; pretty=pretty === true ? 2 : Int(pretty), kw...)
_jsonlines_pretty_check(opts.jsonlines, opts.pretty)
float_style_check(opts.float_style)
float_precision_check(opts.float_style, opts.float_precision)
opts = writeoptions(pretty, kw)
y = StructUtils.lower(opts.style, x)
buf = stringvec(sizeguess(y))
pos = json!(buf, 1, y, opts, Any[y], nothing)
return String(resize!(buf, pos - 1))
end

function json(::Type{Vector{UInt8}}, x; pretty::Union{Integer,Bool}=false, kw...)
opts = writeoptions(pretty, kw)
y = StructUtils.lower(opts.style, x)
buf = Vector{UInt8}(undef, sizeguess(y))
pos = json!(buf, 1, y, opts, Any[y], nothing)
resize!(buf, pos - 1)
return buf
end

function json(fname, obj; kw...)
if obj isa Integer
# special-case for pre-1.0 JSON compat
Expand Down
9 changes: 9 additions & 0 deletions test/json.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ JSON.applyany(::ClosedStyle, f, k, v) = throw(ArgumentError("closed"))
io = IOBuffer()
JSON.json(io, missing)
@test String(take!(io)) == "null"
@test JSON.json(Vector{UInt8}, missing) == b"null"
fname, io = mktemp()
close(io)
JSON.json(fname, missing)
Expand Down Expand Up @@ -332,6 +333,14 @@ JSON.applyany(::ClosedStyle, f, k, v) = throw(ArgumentError("closed"))
@test_throws ArgumentError JSON.json(Float64(π); float_style=:not_a_style)
end

@testset "JSON.json(Vector{UInt8}, x) matches JSON.json(x)" begin
for x in Any[nothing, true, 2, -1.5, "a\"b\\c\u00e9", :sym, [1, 2, 3], (1, "a"), Dict("a" => 1, "b"=>nothing)]
for kw in ((;), (; pretty=true), (; omit_null=true))
@test JSON.json(Vector{UInt8}, x; kw...)::Vector{UInt8} == codeunits(JSON.json(x; kw...))
end
end
end

@testset "Enhanced @omit_null and @omit_empty macros" begin
# Test structs for new macro functionality

Expand Down
Loading