Skip to content

Commit 84fc4cb

Browse files
authored
Merge branch 'master' into nz/writefile
2 parents 3f0bd4d + b2787c3 commit 84fc4cb

File tree

13 files changed

+339
-83
lines changed

13 files changed

+339
-83
lines changed

.github/workflows/CI.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ jobs:
2727
- os: macOS-latest
2828
arch: aarch64
2929
version: 1
30+
- os: ubuntu-latest
31+
arch: x86
32+
version: 1
3033
steps:
3134
- uses: actions/checkout@v5
3235
- uses: julia-actions/setup-julia@v2

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "JSON"
22
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
3-
version = "1.0.0"
3+
version = "1.2.0"
44

55
[deps]
66
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
# JSON.jl
22

3-
This package provides for parsing and printing JSON in pure Julia.
3+
A Julia package for reading and writing JSON data.
44

55
[![Build Status](https://github.com/JuliaIO/JSON.jl/workflows/CI/badge.svg)](https://github.com/JuliaIO/JSON.jl/actions/workflows/CI.yml?query=branch%3Amaster)
66
[![codecov.io](http://codecov.io/github/JuliaIO/JSON.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaIO/JSON.jl?branch=master)
77

88
## Installation
99

10-
Type `] add JSON` and then hit ⏎ Return at the REPL. You should see `pkg> add JSON`.
10+
Install JSON using the Julia package manager:
11+
```julia
12+
import Pkg
13+
Pkg.add("JSON")
14+
```
1115

1216
## Documentation
1317

14-
- [**STABLE**](https://juliaio.github.io/JSON.jl/stable) — **most recently tagged version of the documentation.**
15-
- [**LATEST**](https://juliaio.github.io/JSON.jl/dev) — *in-development version of the documentation.*
16-
17-
Documentation includes extensive guides and examples for reading, writing, and migrating from JSON.jl pre.10 or JSON3.jl.
18+
The [documentation](https://juliaio.github.io/JSON.jl/stable) includes extensive
19+
guides and examples. It also has advice for migrating to JSON.jl v1.0 from
20+
JSON.jl v0.21 or JSON3.jl.
1821

1922
## Basic Usage
2023

@@ -79,9 +82,12 @@ df = DataFrame(Tables.dictrowtable(JSON.parse(resp.body; null=missing, allownan=
7982

8083
## Vendor Directory
8184

82-
This package includes a `vendor/` directory containing a simplified, no-dependency JSON parser (`JSONX`) that can be vendored (copied) into other projects. See the [vendor README](vendor/README.md) for details.
85+
This package includes a `vendor/` directory containing a simplified,
86+
no-dependency JSON parser (`JSONX`) that can be vendored (copied) into other
87+
projects. See the [vendor README](vendor/README.md) for details.
8388

8489
## Contributing and Questions
8590

86-
Contributions are very welcome, as are feature requests and suggestions. Please open an
87-
[issue][https://github.com/JuliaIO/JSON.jl/issues] if you encounter any problems or would just like to ask a question.
91+
Contributions are very welcome, as are feature requests and suggestions. Please
92+
open an [issue](https://github.com/JuliaIO/JSON.jl/issues) if you encounter any
93+
problems or would just like to ask a question.

docs/src/writing.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ JSON.json(pi_value; float_style=:exp, float_precision=3)
171171
# [3.142e+00]
172172
```
173173

174+
`float_precision` must be a positive integer when `float_style` is `:fixed` or `:exp`.
175+
174176
### JSON Lines Format
175177

176178
The JSON Lines format is useful for streaming records where each line is a JSON value:

src/lazy.jl

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ StructUtils.nulllike(::StructUtils.StructStyle, x::LazyValues) = gettype(x) == J
204204

205205
# core method that detects what JSON value is at the current position
206206
# and immediately returns an appropriate LazyValue instance
207-
function _lazy(buf, pos, len, b, opts, isroot=false)
207+
function _lazy(buf, pos::Int, len, b, opts, isroot=false)
208208
if opts.jsonlines
209209
return LazyValue(buf, pos, JSONTypes.ARRAY, opts, isroot)
210210
elseif b == UInt8('{')
@@ -256,7 +256,7 @@ end
256256
# returns a `pos` value that notes the next position where parsing should continue
257257
# this is essentially the `StructUtils.applyeach` implementation for LazyValues w/ type OBJECT
258258
function applyobject(keyvalfunc, x::LazyValues)
259-
pos = getpos(x)
259+
pos::Int = getpos(x)
260260
buf = getbuf(x)
261261
len = getlength(buf)
262262
opts = getopts(x)
@@ -270,17 +270,19 @@ function applyobject(keyvalfunc, x::LazyValues)
270270
b == UInt8('}') && return pos + 1
271271
while true
272272
# parsestring returns key as a PtrString
273-
key, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
274-
@nextbyte
275-
if b != UInt8(':')
276-
error = ExpectedColon
277-
@goto invalid
273+
GC.@preserve buf begin
274+
key, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
275+
@nextbyte
276+
if b != UInt8(':')
277+
error = ExpectedColon
278+
@goto invalid
279+
end
280+
pos += 1
281+
@nextbyte
282+
# we're now positioned at the start of the value
283+
val = _lazy(buf, pos, len, b, opts)
284+
ret = keyvalfunc(key, val)
278285
end
279-
pos += 1
280-
@nextbyte
281-
# we're now positioned at the start of the value
282-
val = _lazy(buf, pos, len, b, opts)
283-
ret = keyvalfunc(key, val)
284286
# if ret is an EarlyReturn, then we're short-circuiting
285287
# parsing via e.g. selection syntax, so return immediately
286288
ret isa StructUtils.EarlyReturn && return ret
@@ -356,7 +358,7 @@ end
356358
# returns a `pos` value that notes the next position where parsing should continue
357359
# this is essentially the `StructUtils.applyeach` implementation for LazyValues w/ type ARRAY
358360
function applyarray(keyvalfunc, x::LazyValues)
359-
pos = getpos(x)
361+
pos::Int = getpos(x)
360362
buf = getbuf(x)
361363
len = getlength(buf)
362364
opts = getopts(x)
@@ -544,7 +546,7 @@ isbigfloat(x::NumberResult) = x.tag == BIGFLOAT
544546

545547
@inline function parsenumber(x::LazyValue)
546548
buf = getbuf(x)
547-
pos = getpos(x)
549+
pos::Int = getpos(x)
548550
len = getlength(buf)
549551
opts = getopts(x)
550552
b = getbyte(buf, pos)
@@ -627,14 +629,14 @@ isbigfloat(x::NumberResult) = x.tag == BIGFLOAT
627629
# if we overflowed, then let's try BigFloat
628630
bres = Parsers.xparse2(BigFloat, buf, startpos, len)
629631
if !Parsers.invalid(bres.code)
630-
return NumberResult(bres.val), startpos + bres.tlen
632+
return NumberResult(bres.val), startpos + Int(bres.tlen)
631633
end
632634
end
633635
if Parsers.invalid(res.code)
634636
error = InvalidNumber
635637
@goto invalid
636638
end
637-
return NumberResult(res.val), startpos + res.tlen
639+
return NumberResult(res.val), Int(startpos + res.tlen)
638640
else
639641
if overflow
640642
return NumberResult(isneg ? -bval : bval), pos
@@ -732,8 +734,11 @@ function Base.show(io::IO, x::LazyValue)
732734
show(io, MIME"text/plain"(), la)
733735
end
734736
elseif T == JSONTypes.STRING
735-
str, _ = parsestring(x)
736-
Base.print(io, "JSON.LazyValue(", repr(convert(String, str)), ")")
737+
buf = getbuf(x)
738+
GC.@preserve buf begin
739+
str, _ = parsestring(x)
740+
Base.print(io, "JSON.LazyValue(", repr(convert(String, str)), ")")
741+
end
737742
elseif T == JSONTypes.NULL
738743
Base.print(io, "JSON.LazyValue(nothing)")
739744
else # bool/number

src/parse.jl

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ parse!(x::LazyValue, obj::T; dicttype::Type{O}=DEFAULT_OBJECT_TYPE, null=nothing
214214
# for LazyValue, if x started at the beginning of the JSON input,
215215
# then we want to ensure that the entire input was consumed
216216
# and error if there are any trailing invalid JSON characters
217-
function checkendpos(x::LazyValue, ::Type{T}, pos) where {T}
217+
function checkendpos(x::LazyValue, ::Type{T}, pos::Int) where {T}
218218
buf = getbuf(x)
219219
len = getlength(buf)
220220
if pos <= len
@@ -277,8 +277,11 @@ function applyvalue(f, x::LazyValues, null)
277277
f(arr)
278278
return pos
279279
elseif type == JSONTypes.STRING
280-
str, pos = parsestring(x)
281-
f(convert(String, str))
280+
buf = getbuf(x)
281+
GC.@preserve buf begin
282+
str, pos = parsestring(x)
283+
f(convert(String, str))
284+
end
282285
return pos
283286
elseif type == JSONTypes.NUMBER
284287
num, pos = parsenumber(x)
@@ -343,9 +346,12 @@ end
343346

344347
function StructUtils.lift(style::StructStyle, ::Type{T}, x::LazyValues, tags=(;)) where {T}
345348
type = gettype(x)
349+
buf = getbuf(x)
346350
if type == JSONTypes.STRING
347-
ptrstr, pos = parsestring(x)
348-
str, _ = StructUtils.lift(style, T, ptrstr, tags)
351+
GC.@preserve buf begin
352+
ptrstr, pos = parsestring(x)
353+
str, _ = StructUtils.lift(style, T, ptrstr, tags)
354+
end
349355
return str, pos
350356
elseif type == JSONTypes.NUMBER
351357
num, pos = parsenumber(x)
@@ -427,7 +433,7 @@ end
427433
@generated function StructUtils.maketuple(st::StructStyle, ::Type{T}, x::LazyValues) where {T<:Tuple}
428434
N = fieldcount(T)
429435
ex = quote
430-
pos = getpos(x)
436+
pos::Int = getpos(x)
431437
buf = getbuf(x)
432438
len = getlength(buf)
433439
opts = getopts(x)
@@ -448,7 +454,9 @@ end
448454
Base.@nexprs $N i -> begin
449455
if typ == JSONTypes.OBJECT
450456
# consume key
451-
_, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
457+
GC.@preserve buf begin
458+
_, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
459+
end
452460
@nextbyte
453461
if b != UInt8(':')
454462
error = ExpectedColon
@@ -483,7 +491,9 @@ end
483491
while true
484492
if typ == JSONTypes.OBJECT
485493
# consume key
486-
_, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
494+
GC.@preserve buf begin
495+
_, pos = @inline parsestring(LazyValue(buf, pos, JSONTypes.STRING, opts, false))
496+
end
487497
@nextbyte
488498
if b != UInt8(':')
489499
error = ExpectedColon

src/write.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ StructUtils.lower(::JSONStyle, x::AbstractArray{<:Any,0}) = x[1]
1616
StructUtils.lower(::JSONStyle, x::AbstractArray{<:Any, N}) where {N} = (view(x, ntuple(_ -> :, N - 1)..., j) for j in axes(x, N))
1717
StructUtils.lower(::JSONStyle, x::AbstractVector) = x
1818

19+
# for pre-1.0 compat, which serialized Tuple object keys by default
20+
StructUtils.lowerkey(::JSONStyle, x::Tuple) = string(x)
21+
1922
"""
2023
JSON.omit_null(::Type{T})::Bool
2124
JSON.omit_null(::JSONStyle, ::Type{T})::Bool
@@ -393,6 +396,9 @@ end
393396
@noinline float_style_throw(fs) = throw(ArgumentError("Invalid float style: $fs"))
394397
float_style_check(fs) = fs == :shortest || fs == :fixed || fs == :exp || float_style_throw(fs)
395398

399+
@noinline float_precision_throw(fs, fp) = throw(ArgumentError("float_precision must be positive when float_style is $fs; got $fp"))
400+
float_precision_check(fs, fp) = (fs == :shortest || fp > 0) || float_precision_throw(fs, fp)
401+
396402
# if jsonlines and pretty is not 0 or false, throw an ArgumentError
397403
@noinline _jsonlines_pretty_throw() = throw(ArgumentError("pretty printing is not supported when writing jsonlines"))
398404
_jsonlines_pretty_check(jsonlines, pretty) = jsonlines && pretty !== false && !iszero(pretty) && _jsonlines_pretty_throw()
@@ -401,6 +407,7 @@ function json(io::IO, x::T; pretty::Union{Integer,Bool}=false, kw...) where {T}
401407
opts = WriteOptions(; pretty=pretty === true ? 2 : Int(pretty), kw...)
402408
_jsonlines_pretty_check(opts.jsonlines, opts.pretty)
403409
float_style_check(opts.float_style)
410+
float_precision_check(opts.float_style, opts.float_precision)
404411
y = StructUtils.lower(opts.style, x)
405412
# Use smaller initial buffer size, limited by bufsize
406413
initial_size = min(sizeguess(y), opts.bufsize)
@@ -423,6 +430,7 @@ function json(x; pretty::Union{Integer,Bool}=false, kw...)
423430
opts = WriteOptions(; pretty=pretty === true ? 2 : Int(pretty), kw...)
424431
_jsonlines_pretty_check(opts.jsonlines, opts.pretty)
425432
float_style_check(opts.float_style)
433+
float_precision_check(opts.float_style, opts.float_precision)
426434
y = StructUtils.lower(opts.style, x)
427435
buf = stringvec(sizeguess(y))
428436
pos = json!(buf, 1, y, opts, Any[y], nothing)
@@ -619,11 +627,9 @@ function json!(buf, pos, x, opts::WriteOptions, ancestor_stack::Union{Nothing, V
619627
if wroteany
620628
pos -= 1
621629
pos = indent(buf, pos, local_ind, depth, io, bufsize)
622-
else
623-
# but if the object/array was empty, we need to do the check manually
624-
@checkn 1
625630
end
626631
# even if the input is empty and we're jsonlines, the spec says it's ok to end w/ a newline
632+
@checkn 1
627633
@inbounds buf[pos] = opts.jsonlines ? UInt8('\n') : al ? UInt8(']') : UInt8('}')
628634
return pos + 1
629635
else
@@ -764,4 +770,4 @@ function anyinvalidnumberchars(x)
764770
end
765771
end
766772
return false
767-
end
773+
end

0 commit comments

Comments
 (0)