Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
61 changes: 61 additions & 0 deletions src/JSON.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,67 @@ print(io::IO, obj, indent=nothing) = json(io, obj; pretty=something(indent, 0))
print(a, indent=nothing) = print(stdout, a, indent)
@doc (@doc json) print

"""
JSON.writefile(Vector{UInt8}, x; kw...)::Vector{UInt8}
JSON.writefile(io::IO, x; kw...)
JSON.writefile(filename::AbstractString, x; kw...)

Serialize `x` to JSON format. This function provides the same functionality as [`JSON.json`](@ref)

The first method returns the JSON output as a `Vector{UInt8}`.
The second method writes JSON output to an `IO` object.
The third method writes JSON output to a file specified by `filename` which will
be created if it does not exist yet or overwritten if it does exist.

This function is provided for backward compatibility when upgrading from
older versions of JSON.jl where the `json` function signature differed.
For new code, prefer using [`JSON.json`](@ref) directly.

All methods accept the same keyword arguments as [`JSON.json`](@ref):

- `omit_null`, `omit_empty`, `allownan`, `jsonlines`, `pretty`, `inline_limit`
- `ninf`, `inf`, `nan`, `float_style`, `float_precision`, `bufsize`, `style`

See [`JSON.json`](@ref) for detailed documentation of all keyword arguments and more examples.

# Examples
```julia
# Write to IO
io = IOBuffer()
JSON.writefile(io, Dict("key" => "value"))
String(take!(io)) # "{\"key\":\"value\"}"

# Write to file
JSON.writefile("output.json", [1, 2, 3])

# Get as bytes
bytes = JSON.writefile(Vector{UInt8}, Dict("hello" => "world"))
String(bytes) # "{\"hello\":\"world\"}"
```
"""
function writefile end

function writefile(::Type{Vector{UInt8}}, x; pretty::Union{Integer,Bool}=false, kw...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following why we want this or the 2nd definitions? writefile that doesn't write to a file and just returns a byte vector?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to avoid extra conversions between String and Vector{UInt8} for use with Zarr.jl and SmallZarrGroups.jl

I'm also trying to match the style of the writefile function in Parquet2.jl https://expandingman.gitlab.io/Parquet2.jl/#Writing-Data

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looking at this again, writefile isn't a good name for the function, so I changed it to write_json.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a benchmark on the cost of doing the extra String to Vector{UInt8} conversion:

julia> using Chairmarks, JSON, Random

julia> @be randstring(10000) Vector{UInt8}(JSON.json(_)) seconds=2
Benchmark: 52143 samples with 4 evaluations
 min    4.807 μs (7 allocs: 19.852 KiB)
 median 4.874 μs (7 allocs: 19.852 KiB)
 mean   5.566 μs (7 allocs: 19.856 KiB, 0.82% gc time)
 max    15.658 ms (7 allocs: 19.883 KiB, 99.92% gc time)

julia> @be randstring(10000) JSON.write_json(Vector{UInt8}, _) seconds=2
Benchmark: 50390 samples with 7 evaluations
 min    2.971 μs (4 allocs: 9.914 KiB)
 median 3.006 μs (4 allocs: 9.914 KiB)
 mean   3.394 μs (4 allocs: 9.919 KiB, 0.82% gc time)
 max    8.692 ms (4 allocs: 9.945 KiB, 99.92% gc time)

opts = WriteOptions(; pretty=pretty === true ? 2 : Int(pretty), kw...)
_jsonlines_pretty_check(opts.jsonlines, opts.pretty)
float_style_check(opts.float_style)
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 writefile(io::IO, x; kw...)
json(io, x; kw...)
end

function writefile(filename::AbstractString, x; kw...)
open(filename; write=true) do io
writefile(io, x; kw...)
end
end

@compile_workload begin
x = JSON.parse("{\"a\": 1, \"b\": null, \"c\": true, \"d\": false, \"e\": \"\", \"f\": [1,null,true], \"g\": {\"key\": \"value\"}}")
json = JSON.json(x)
Expand Down
15 changes: 15 additions & 0 deletions test/json.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,21 @@ end
# inline_limit tests
@test JSON.json([1, 2]; pretty=2, inline_limit=3) == "[1,2]"
@test JSON.json([1, 2, 3]; pretty=2, inline_limit=3) == "[\n 1,\n 2,\n 3\n]"
# writefile
for (obj, kw, output) in [
([1, 2, 3], (;), b"[1,2,3]"),
([1, 2, 3], (;pretty=true), b"[\n 1,\n 2,\n 3\n]"),
(NaN, (;allownan=true), b"NaN"),
(NaN, (;allownan=true, nan="different nan string"), b"different nan string"),
]
@test JSON.writefile(Vector{UInt8}, obj; kw...) == output
io = IOBuffer()
@test JSON.writefile(io, obj; kw...) === nothing
@test take!(io) == output
fname = tempname()
@test JSON.writefile(fname, obj; kw...) === nothing
@test read(fname) == output
end
end
# non-Integer/AbstractFloat but <: Real output
@test_throws MethodError JSON.json(CustomNumber(3.14))
Expand Down
Loading