From 2a4979d8f3757a8c699b2188b8ce9d5e46140747 Mon Sep 17 00:00:00 2001 From: tan Date: Mon, 1 Dec 2025 14:07:03 +0530 Subject: [PATCH 1/2] feat: json 1.x compat Added compat changes for JSON 1.x. --- src/batchimages.jl | 2 +- src/jobs/jobs.jl | 2 +- src/jobs/logging-legacy.jl | 2 +- src/node.jl | 2 +- src/restapi.jl | 2 +- src/utils.jl | 15 ++++++++------- test/jobs-exposed-port-live.jl | 4 ++-- test/jobs-live.jl | 14 +++++++------- test/jobs-windows-live.jl | 2 +- test/mocking.jl | 8 ++++---- 10 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/batchimages.jl b/src/batchimages.jl index 8fedfff31..1318c790c 100644 --- a/src/batchimages.jl +++ b/src/batchimages.jl @@ -141,7 +141,7 @@ function _batchimages_legacy(auth::Authentication) r = _restcall(auth, :GET, "juliaruncloud", "get_image_options") if r.status == 200 try - json = JSON.parse(String(r.body)) + json = JSON.parse(String(r.body); dicttype=Dict) if json["success"] && haskey(json, "image_options") image_options = json["image_options"] default_options = [ diff --git a/src/jobs/jobs.jl b/src/jobs/jobs.jl index 5c3cdd17a..97b5450a0 100644 --- a/src/jobs/jobs.jl +++ b/src/jobs/jobs.jl @@ -233,7 +233,7 @@ struct Job Dict{String, Any}() # TODO: drop Nothing? else try - JSON.parse(unescape_string(inputs)) + JSON.parse(unescape_string(inputs); dicttype=Dict) catch e throw( JuliaHubError("Unable to parse 'inputs' JSON for job $jobname:\n$(inputs)", diff --git a/src/jobs/logging-legacy.jl b/src/jobs/logging-legacy.jl index c8d486d45..11692797c 100644 --- a/src/jobs/logging-legacy.jl +++ b/src/jobs/logging-legacy.jl @@ -223,7 +223,7 @@ function _get_job_logs_legacy( # If the request was successful, we should be able to parse the logs. But if there was an error, # we might also get back a JSON with a success=false field. body = String(r.body) - jb = JSON.parse(body) + jb = JSON.parse(body; dicttype=Dict) if jb isa Dict && !get(jb, "success", true) throw( JuliaHubError( diff --git a/src/node.jl b/src/node.jl index 24294f8d8..bae6bb525 100644 --- a/src/node.jl +++ b/src/node.jl @@ -81,7 +81,7 @@ function nodespecs(; auth::Authentication=__auth__()) r = _api_nodespecs(auth) if r.status == 200 try - json = JSON.parse(String(r.body)) + json = JSON.parse(String(r.body); dicttype=Dict) if json["success"] nodes = [ NodeSpec(n) for n in json["node_specs"] diff --git a/src/restapi.jl b/src/restapi.jl index 5ebdb854c..e9bd96990 100644 --- a/src/restapi.jl +++ b/src/restapi.jl @@ -51,7 +51,7 @@ function Base.getproperty(r::_RESTResponse, name::Symbol) if name in fieldnames(_RESTResponse) getfield(r, name) elseif name == :json - JSON.parse(getfield(r, :body)) + JSON.parse(getfield(r, :body); dicttype=Dict) else error("_RESTResponse has no property $name") end diff --git a/src/utils.jl b/src/utils.jl index 8841df66c..889c1fb84 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -147,7 +147,7 @@ function _parse_response_json(r::HTTP.Response, ::Type{T})::Tuple{T, String} whe end function _parse_response_json(s::AbstractString, ::Type{T})::Tuple{T, String} where {T} object = try - JSON.parse(s) + JSON.parse(s; dicttype=Dict) catch e throw( JuliaHubError( @@ -167,7 +167,7 @@ function _parse_response_json(s::AbstractString, ::Type{T})::Tuple{T, String} wh end function _get_json( - json::Dict, key::AbstractString, ::Type{T}; msg::Union{AbstractString, Nothing}=nothing + json::AbstractDict, key::AbstractString, ::Type{T}; msg::Union{AbstractString, Nothing}=nothing )::T where {T} value = get(json, key) do errormsg = """ @@ -186,7 +186,7 @@ function _get_json( end function _get_json_or( - json::Dict, + json::AbstractDict, key::AbstractString, ::Type{T}, default::U=nothing; @@ -209,7 +209,8 @@ end # A key point, though, is that it will throw a JuliaHubError if the server response is somehow # invalid and we can't parse/convert it properly. function _get_json_convert( - json::Dict, key::AbstractString, ::Type{UUIDs.UUID}; msg::Union{AbstractString, Nothing}=nothing + json::AbstractDict, key::AbstractString, ::Type{UUIDs.UUID}; + msg::Union{AbstractString, Nothing}=nothing )::UUIDs.UUID uuid_str = _get_json(json, key, String; msg) uuid = tryparse(UUIDs.UUID, uuid_str) @@ -355,7 +356,7 @@ function _walkfiles(f::Base.Callable, root::AbstractString; descend::Base.Callab end end -function _json_get(d::Dict, key, ::Type{T}; var::AbstractString, parse=false) where {T} +function _json_get(d::AbstractDict, key, ::Type{T}; var::AbstractString, parse=false) where {T} haskey(d, key) || _throw_jsonerror(var, "key `$key` missing", d) if parse isa(d[key], AbstractString) || _throw_jsonerror( @@ -372,7 +373,7 @@ function _json_get(d::Dict, key, ::Type{T}; var::AbstractString, parse=false) wh end end -function _throw_jsonerror(var::AbstractString, msg::AbstractString, json::Dict) +function _throw_jsonerror(var::AbstractString, msg::AbstractString, json::AbstractDict) e = JuliaHubError( """ Invalid JSON response from JuliaHub ($var): $msg @@ -383,7 +384,7 @@ function _throw_jsonerror(var::AbstractString, msg::AbstractString, json::Dict) end # Checks that the 'success' field is set and === true -function _json_check_success(json::Dict; var::AbstractString) +function _json_check_success(json::AbstractDict; var::AbstractString) success = _json_get(json, "success", Bool; var) success || throw(JuliaHubError( """ diff --git a/test/jobs-exposed-port-live.jl b/test/jobs-exposed-port-live.jl index 4a5cbc9af..9304bb72d 100644 --- a/test/jobs-exposed-port-live.jl +++ b/test/jobs-exposed-port-live.jl @@ -19,7 +19,7 @@ function test_job_with_exposed_port(job::JuliaHub.Job; check_input::Bool=false, job = wait_submission(job) let r = wait_exposed_job_502(job) @test r.status == 200 - json = JSON.parse(String(r.body)) + json = JSON.parse(String(r.body); dicttype=Dict) @test json isa AbstractDict @test get(json, "success", nothing) === true @test get(json, "port", nothing) == port @@ -34,7 +34,7 @@ function test_job_with_exposed_port(job::JuliaHub.Job; check_input::Bool=false, # gets incremented correctly. let r = JuliaHub.request(job, "GET", "/"; auth, status_exception=false) @test r.status == 200 - json = JSON.parse(String(r.body)) + json = JSON.parse(String(r.body); dicttype=Dict) @test json isa AbstractDict @test get(json, "success", nothing) === true @test get(json, "nrequests", nothing) == 2 diff --git a/test/jobs-live.jl b/test/jobs-live.jl index c29618baf..0d9e37452 100644 --- a/test/jobs-live.jl +++ b/test/jobs-live.jl @@ -199,7 +199,7 @@ end @test job.status == "Failed" # Even though the job failed, any RESULTS set before the error are still stored @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "x") @test results["x"] == 42 @@ -220,7 +220,7 @@ end job = JuliaHub.wait_job(job) @test test_job_done_and_not_failed(job, "Completed") @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "vs") @test length(results["vs"]) == 2 @@ -246,7 +246,7 @@ end job = JuliaHub.wait_job(job) @test test_job_done_and_not_failed(job, "Completed") @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "vs") @test length(results["vs"]) == 5 @@ -268,7 +268,7 @@ end job = JuliaHub.wait_job(job) @test test_job_done_and_not_failed(job, "Completed") @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "datastructures_version") @test VersionNumber(results["datastructures_version"]) == v"0.17.0" @@ -288,7 +288,7 @@ end job = JuliaHub.wait_job(job) @test test_job_done_and_not_failed(job, "Completed") @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "datastructures_version") @test VersionNumber(results["datastructures_version"]) > v"0.17.0" @@ -315,7 +315,7 @@ if v"1.10" <= Base.VERSION < v"1.12" @test JuliaHub.job_file(job, :input, "appbundle.tar") isa JuliaHub.JobFile # Test the results values @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "datastructures_version") @test VersionNumber(results["datastructures_version"]) == v"0.17.0" @@ -432,7 +432,7 @@ end @test test_job_done_and_not_failed(job, "Completed") @test job._json["sysimage_build"] === true @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test results["in_sysimage"] === true @test results["loaded_modules_before_import"] === true diff --git a/test/jobs-windows-live.jl b/test/jobs-windows-live.jl index 7b82a591c..d3a0d2fa4 100644 --- a/test/jobs-windows-live.jl +++ b/test/jobs-windows-live.jl @@ -26,7 +26,7 @@ @test job.status == "Completed" @test job.alias == full_alias @test !isempty(job.results) - let results = JSON.parse(job.results) + let results = JSON.parse(job.results; dicttype=Dict) @test results isa AbstractDict @test haskey(results, "iswindows") @test results["iswindows"] === true diff --git a/test/mocking.jl b/test/mocking.jl index 794a33cb0..c4b77dc9d 100644 --- a/test/mocking.jl +++ b/test/mocking.jl @@ -350,7 +350,7 @@ function _restcall_mocked(method, url, headers, payload; query) ) |> jsonresponse(200) end elseif (method == :POST) && endswith(url, "juliaruncloud/extend_job_time_limit") - payload = JSON.parse(payload) + payload = JSON.parse(payload; dicttype=Dict) idx = findfirst(isequal(payload["jobname"]), job_names) if isnothing(idx) JuliaHub._RESTResponse(403, "User does not have access to this job") @@ -401,7 +401,7 @@ function _restcall_mocked(method, url, headers, payload; query) dataset = URIs.unescapeuri(match(DATASET_REGEX, url)[1]) if "username/$(dataset)" ∈ existing_datasets if haskey(MOCK_JULIAHUB_STATE, :dataset_params) - merge!(MOCK_JULIAHUB_STATE[:dataset_params], JSON.parse(payload)) + merge!(MOCK_JULIAHUB_STATE[:dataset_params], JSON.parse(payload; dicttype=Dict)) end Dict{String, Any}( "name" => dataset, @@ -411,7 +411,7 @@ function _restcall_mocked(method, url, headers, payload; query) return JuliaHub._RESTResponse(404, "Dataset $(dataset) does not exist.") end elseif (method == :POST) && endswith(url, "/user/datasets") - payload = JSON.parse(payload) + payload = JSON.parse(payload; dicttype=Dict) dataset, type = payload["name"], payload["type"] if "$(MOCK_USERNAME)/$(dataset)" in existing_datasets JuliaHub._RESTResponse(409, "Dataset $(dataset) exists") @@ -426,7 +426,7 @@ function _restcall_mocked(method, url, headers, payload; query) dataset, is_user = let m = match(DATASET_VERSIONS_REGEX, url) URIs.unescapeuri(m[2]), m[1] == "user/" end - payload = JSON.parse(something(payload, "{}")) + payload = JSON.parse(something(payload, "{}"); dicttype=Dict) if isempty(payload) || !haskey(payload, "action") is_existing_dataset = if is_user "$(MOCK_USERNAME)/$(dataset)" in existing_datasets From 81d7c075905e97b031794cee63f2bf6ce9db8726 Mon Sep 17 00:00:00 2001 From: tan Date: Mon, 1 Dec 2025 14:47:54 +0530 Subject: [PATCH 2/2] fix format --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 889c1fb84..94794034a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -210,7 +210,7 @@ end # invalid and we can't parse/convert it properly. function _get_json_convert( json::AbstractDict, key::AbstractString, ::Type{UUIDs.UUID}; - msg::Union{AbstractString, Nothing}=nothing + msg::Union{AbstractString, Nothing}=nothing, )::UUIDs.UUID uuid_str = _get_json(json, key, String; msg) uuid = tryparse(UUIDs.UUID, uuid_str)