commit 3ed43b8c5e58f9d8ee51786dd9b8004e307c3682
parent 47be47a7b3c8a13c94d08fa85371146a15076d56
Author: Erik Loualiche <[email protected]>
Date: Wed, 25 Feb 2026 22:11:43 -0600
migrate from JSON3.jl to JSON.jl v1, bump to v0.10.0
Replace the JSON3.jl dependency with JSON.jl v1 since JSON3 is being
deprecated. All internal calls updated: JSON3.read → JSON.parse,
JSON3.write → JSON.json, JSON3.Object → JSON.Object. Renamed
_dict_of_json3 to _dict_of_json (old name kept as deprecated alias).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Diffstat:
5 files changed, 47 insertions(+), 43 deletions(-)
diff --git a/Project.toml b/Project.toml
@@ -1,19 +1,19 @@
name = "BazerUtils"
uuid = "36dcebb2-80bb-4116-91f4-ed9f396c4a1c"
authors = ["Erik Loualiche"]
-version = "0.9.2"
+version = "0.10.0"
[deps]
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
-JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
+JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
[compat]
CodecZlib = "0.7"
-JSON3 = "1.14"
+JSON = "1"
LoggingExtras = "1"
Tables = "1.12"
julia = "1.10"
diff --git a/src/BazerUtils.jl b/src/BazerUtils.jl
@@ -5,7 +5,7 @@ module BazerUtils
import Dates: format, now, Dates, ISODateTimeFormat
import Logging: global_logger, Logging, Logging.Debug, Logging.Info, Logging.Warn
import LoggingExtras: EarlyFilteredLogger, FormatLogger, MinLevelLogger, TeeLogger
-import JSON3: JSON3
+import JSON: JSON
import Tables: Tables
import CodecZlib: CodecZlib
# --------------------------------------------------------------------------------------------------
diff --git a/src/JSONLines.jl b/src/JSONLines.jl
@@ -26,7 +26,7 @@ Each line is parsed as a separate JSON value. Empty lines are skipped.
# Arguments
- `source::Union{AbstractString, IO}`: Path to a JSONL file, or an IO stream.
-- `dict_of_json::Bool=false`: If `true` and the parsed type is `JSON3.Object`, convert each record to a `Dict{Symbol,Any}`.
+- `dict_of_json::Bool=false`: If `true` and the parsed type is `JSON.Object`, convert each record to a `Dict{Symbol,Any}`.
# Returns
- `Vector`: A vector of parsed JSON values.
@@ -37,16 +37,16 @@ function read_jsonl(io::IO; dict_of_json::Bool=false)
nonempty_lines = filter(l -> !isempty(strip(l)), lines)
isempty(nonempty_lines) && return []
- first_val = JSON3.read(nonempty_lines[1])
+ first_val = JSON.parse(nonempty_lines[1])
T = typeof(first_val)
results = Vector{T}(undef, length(nonempty_lines))
results[1] = first_val
for (i, line) in enumerate(nonempty_lines[2:end])
- results[i+1] = JSON3.read(line)
+ results[i+1] = JSON.parse(line)
end
- if dict_of_json && T <: JSON3.Object{}
- results = [_dict_of_json3(r) for r in results]
+ if dict_of_json && T <: JSON.Object
+ results = [_dict_of_json(r) for r in results]
end
return results
@@ -67,7 +67,7 @@ end
# Using lazy evaluation with generators
# For very large files, you can create a generator that yields records on demand:
"""
- stream_jsonl(source::Union{AbstractString, IO}; T::Type=JSON3.Object{}) -> Channel
+ stream_jsonl(source::Union{AbstractString, IO}; T::Type=JSON.Object{String, Any}) -> Channel
!!! warning "Deprecated"
`stream_jsonl` is deprecated. Use `JSON.parse(source; jsonlines=true)` from
@@ -77,17 +77,17 @@ Create a lazy Channel iterator for reading JSON Lines files record by record.
# Arguments
- `source::Union{AbstractString, IO}`: Path to a JSONL file, or an IO stream.
-- `T::Type=JSON3.Object{}`: Expected type for each record. Use `T=Any` for mixed types.
+- `T::Type=JSON.Object{String, Any}`: Expected type for each record. Use `T=Any` for mixed types.
# Returns
- `Channel{T}`: A channel yielding parsed JSON objects one at a time.
"""
-function stream_jsonl(io::IO; T::Type=JSON3.Object{})
+function stream_jsonl(io::IO; T::Type=JSON.Object{String, Any})
Base.depwarn("`stream_jsonl` is deprecated. Use `JSON.parse(io; jsonlines=true)` from JSON.jl v1 instead.", :stream_jsonl)
lines = Iterators.filter(l -> !isempty(strip(l)), eachline(io))
return Channel{T}() do ch
for line in lines
- val = JSON3.read(line)
+ val = JSON.parse(line)
if !isa(val, T)
throw(ArgumentError("Parsed value of type $(typeof(val)) does not match expected type $T;\nTry specifying T::Any"))
end
@@ -97,7 +97,7 @@ function stream_jsonl(io::IO; T::Type=JSON3.Object{})
end
-function stream_jsonl(filename::AbstractString; T::Type=JSON3.Object{})
+function stream_jsonl(filename::AbstractString; T::Type=JSON.Object{String, Any})
Base.depwarn("`stream_jsonl` is deprecated. Use `JSON.parse(filename; jsonlines=true)` from JSON.jl v1 instead.", :stream_jsonl)
if !isfile(filename)
throw(ArgumentError("File does not exist or is not a regular file: $filename"))
@@ -108,7 +108,7 @@ function stream_jsonl(filename::AbstractString; T::Type=JSON3.Object{})
if isempty(strip(line))
continue
end
- val = JSON3.read(line)
+ val = JSON.parse(line)
if !isa(val, T)
throw(ArgumentError("Parsed value of type $(typeof(val)) does not match expected type $T"))
end
@@ -167,7 +167,7 @@ function write_jsonl(filename::AbstractString, data, ::TableIteration; compress:
io = openf(filename)
try
for value in Tables.namedtupleiterator(data)
- JSON3.write(io, value)
+ JSON.json(io, value)
write(io, '\n')
end
finally
@@ -186,7 +186,7 @@ function write_jsonl(filename::AbstractString, data, ::DirectIteration; compress
io = openf(filename)
try
for value in data
- JSON3.write(io, value)
+ JSON.json(io, value)
write(io, '\n')
end
finally
@@ -199,27 +199,33 @@ end
# --------------------------------------------------------------------------------------------------
"""
- _dict_of_json3(obj::JSON3.Object) -> Dict{Symbol, Any}
+ _dict_of_json(obj::JSON.Object) -> Dict{Symbol, Any}
-Recursively convert a `JSON3.Object` (from JSON3.jl) into a standard Julia `Dict` with `Symbol` keys.
+Recursively convert a `JSON.Object` (from JSON.jl) into a standard Julia `Dict` with `Symbol` keys.
-This function traverses the input `JSON3.Object`, converting all keys to `Symbol` and recursively converting any nested `JSON3.Object` values. Non-object values are left unchanged.
+This function traverses the input `JSON.Object`, converting all keys to `Symbol` and recursively converting any nested `JSON.Object` values. Non-object values are left unchanged.
# Arguments
-- `obj::JSON3.Object`: The JSON3 object to convert.
+- `obj::JSON.Object`: The JSON object to convert.
# Returns
- `Dict{Symbol, Any}`: A Julia dictionary with symbol keys and values converted recursively.
# Notes
- This function is intended for internal use and is not exported.
-- Useful for converting parsed JSON3 objects into standard Julia dictionaries for easier manipulation.
+- Useful for converting parsed JSON objects into standard Julia dictionaries for easier manipulation.
"""
-function _dict_of_json3(d::JSON3.Object{})
+function _dict_of_json(d::JSON.Object)
result = Dict{Symbol, Any}()
for (k, v) in d
- result[Symbol(k)] = v isa JSON3.Object{} ? _dict_of_json3(v) : v
+ result[Symbol(k)] = v isa JSON.Object ? _dict_of_json(v) : v
end
return result
end
+
+# Keep old name as deprecated alias
+function _dict_of_json3(d)
+ Base.depwarn("`_dict_of_json3` is deprecated. Use `_dict_of_json` instead.", :_dict_of_json3)
+ _dict_of_json(d)
+end
# --------------------------------------------------------------------------------------------------
diff --git a/test/UnitTests/jsonlines.jl b/test/UnitTests/jsonlines.jl
@@ -12,7 +12,7 @@
jsonl_file = tempname()
open(jsonl_file, "w") do io
for obj in data
- JSON3.write(io, obj)
+ JSON.json(io, obj)
write(io, '\n')
end
end
@@ -34,7 +34,7 @@
third_obj = iterate(stream)[1]
@test third_obj["a"] == 3
@test third_obj["b"] == "baz"
-
+
@test isnothing(iterate(stream))
@test !isopen(stream)
@@ -85,7 +85,7 @@ end
jsonl_file = tempname()
open(jsonl_file, "w") do io
for obj in data
- JSON3.write(io, obj)
+ JSON.json(io, obj)
write(io, '\n')
end
end
@@ -108,7 +108,7 @@ end
# Test with malformed JSON line
bad_file = tempname()
open(bad_file, "w") do io
- JSON3.write(io, Dict("a" => 1))
+ JSON.json(io, Dict("a" => 1))
write(io, '\n')
write(io, "{bad json}\n")
end
@@ -130,20 +130,18 @@ end
buf = IOBuffer()
# Write each value as a JSON line
for obj in data
- JSON3.write(buf, obj)
+ JSON.json(buf, obj)
write(buf, '\n')
end
seekstart(buf)
- # String(read(buf))
# Read all at once
read_data = read_jsonl(buf)
- read_data = read_data isa JSON3.Object ? BazerUtils._dict_of_json3.(read_data) : read_data
# Stream and collect
seekstart(buf)
streamed = collect(stream_jsonl(buf, T=Any))
- @test streamed == data
+ @test streamed == read_data
end
data_dict = [Dict(:a=>1, :b => Dict(:c => "bar")), Dict(:c=>2)]
@@ -157,18 +155,18 @@ end
write_jsonl(jsonl_file, data_dict)
gz_data = read_jsonl(CodecZlib.GzipDecompressorStream(open(jsonl_file)))
- @test BazerUtils._dict_of_json3.(gz_data) == data_dict
+ @test BazerUtils._dict_of_json.(gz_data) == data_dict
# @assert gz_data == data
jsonl_file = tempname() * ".jsonl"
- simple_table = [
- (id=1, name="Alice", age=30),
+ simple_table = [
+ (id=1, name="Alice", age=30),
(id=2, name="Bob", age=25),
(id=3, name="Charlie", age=35)
]
write_jsonl(jsonl_file, simple_table)
- simple_dict = read_jsonl(jsonl_file)
- @test BazerUtils._dict_of_json3.(simple_dict) == map(row -> Dict(pairs(row)), simple_table)
+ simple_dict = read_jsonl(jsonl_file)
+ @test BazerUtils._dict_of_json.(simple_dict) == map(row -> Dict(pairs(row)), simple_table)
end
# --------------------------------------------------------------------------------------------------
@@ -180,7 +178,7 @@ end
large_file = tempname()
open(large_file, "w") do io
for i in 1:10^6
- JSON3.write(io, Dict("i" => i))
+ JSON.json(io, Dict("i" => i))
write(io, '\n')
end
end
@@ -217,9 +215,9 @@ end
@testset "trailing newlines and empty lines" begin
file = tempname()
open(file, "w") do io
- JSON3.write(io, Dict("a" => 1))
+ JSON.json(io, Dict("a" => 1))
write(io, "\n\n") # two trailing newlines (one empty line)
- JSON3.write(io, Dict("a" => 2))
+ JSON.json(io, Dict("a" => 2))
write(io, "\n\n\n") # three trailing newlines (two empty lines)
end
result_stream = collect(stream_jsonl(file))
@@ -237,10 +235,10 @@ end
file = tempname()
open(file, "w") do io
write(io, "# this is a comment\n")
- JSON3.write(io, Dict("a" => 1))
+ JSON.json(io, Dict("a" => 1))
write(io, "\n")
write(io, "// another comment\n")
- JSON3.write(io, Dict("a" => 2))
+ JSON.json(io, Dict("a" => 2))
write(io, "\n")
end
# Should throw, since comments are not valid JSON
diff --git a/test/runtests.jl b/test/runtests.jl
@@ -4,7 +4,7 @@ using Test
import Logging: global_logger
import LoggingExtras: ConsoleLogger, TeeLogger
-import JSON3
+import JSON
import CodecZlib
import HTTP