BazerUtils.jl

Assorted Julia utilities including custom logging
Log | Files | Refs | README | LICENSE

commit 3ed43b8c5e58f9d8ee51786dd9b8004e307c3682
parent 47be47a7b3c8a13c94d08fa85371146a15076d56
Author: Erik Loualiche <eloualic@umn.edu>
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 <noreply@anthropic.com>

Diffstat:
MProject.toml | 6+++---
Msrc/BazerUtils.jl | 2+-
Msrc/JSONLines.jl | 46++++++++++++++++++++++++++--------------------
Mtest/UnitTests/jsonlines.jl | 34++++++++++++++++------------------
Mtest/runtests.jl | 2+-
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