From 369e3db5f9d000f6e4622c237ae6c3a2fc590b7c Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:42:37 -0600 Subject: [PATCH 01/13] drop json3 dependency --- Project.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 3f89ed780..92a99ab66 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,7 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" H5Zblosc = "c8ec2601-a99c-407f-b158-e79c03c2f5f7" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" @@ -24,7 +24,6 @@ SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" StringTemplates = "59c22e0c-4e04-425f-9782-11a552c58a8d" -StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" TerminalLoggers = "5d786b92-1e48-4d6f-9151-6b4477ca9bed" @@ -42,7 +41,7 @@ DocStringExtensions = "0.8, 0.9" H5Zblosc = "0.1" HDF5 = "0.17" InteractiveUtils = "1" -JSON3 = "^1.11" +JSON = "^1.5" LinearAlgebra = "1" Logging = "1" Mustache = "1" @@ -54,7 +53,6 @@ SHA = "0.7" SQLite = "^1.6" Serialization = "1" StringTemplates = "0.1.0" -StructTypes = "^1.9" TOML = "1" Tables = "^1.11" TerminalLoggers = "~0.1" From a28cc5a6053d34fe1e8a7ad9afe676ba78d8847f Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:43:32 -0600 Subject: [PATCH 02/13] change API --- src/abstract_time_series.jl | 4 ++-- src/serialization.jl | 16 ++++++++-------- src/time_series_metadata_store.jl | 12 ++++++------ src/time_series_parser.jl | 2 +- src/utils/generate_struct_files.jl | 8 ++------ src/utils/generate_structs.jl | 2 +- src/utils/recorder_events.jl | 6 +++--- src/validation.jl | 2 +- 8 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/abstract_time_series.jl b/src/abstract_time_series.jl index 4a6029d4f..94fa2d478 100644 --- a/src/abstract_time_series.jl +++ b/src/abstract_time_series.jl @@ -17,13 +17,13 @@ end function make_features_string(features::Dict{String, Union{Bool, Int, String}}) key_names = sort!(collect(keys(features))) data = [Dict(k => features[k]) for k in key_names] - return JSON3.write(data) + return JSON.json(data) end function make_features_string(; features...) key_names = sort!(collect(string.(keys(features)))) data = [Dict(k => features[Symbol(k)]) for (k) in key_names] - return JSON3.write(data) + return JSON.json(data) end abstract type ForecastMetadata <: TimeSeriesMetadata end diff --git a/src/serialization.jl b/src/serialization.jl index 8afccc317..1fb0777d7 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -33,10 +33,10 @@ function to_json(obj::T; pretty = false, indent = 2) where {T <: InfrastructureS try if pretty io = IOBuffer() - JSON3.pretty(io, serialize(obj), JSON3.AlignmentContext(; indent = indent)) + JSON.json(io, serialize(obj); pretty = indent) return take!(io) else - return JSON3.write(serialize(obj)) + return JSON.json(serialize(obj)) end catch e @error "Failed to serialize $(summary(obj))" @@ -52,9 +52,9 @@ function to_json( ) where {T <: InfrastructureSystemsType} data = serialize(obj) if pretty - res = JSON3.pretty(io, data, JSON3.AlignmentContext(; indent = indent)) + res = JSON.json(io, data; pretty = indent) else - res = JSON3.write(io, data) + res = JSON.json(io, data) end return res @@ -73,7 +73,7 @@ end Deserializes a InfrastructureSystemsType from String or IO. """ function from_json(io::Union{IO, String}, ::Type{T}) where {T <: InfrastructureSystemsType} - return deserialize(T, JSON3.read(io, Dict)) + return deserialize(T, JSON.parse(io; dicttype = Dict{String, Any})) end """ @@ -271,7 +271,7 @@ deserialize(::Type{Dates.DateTime}, val::AbstractString) = Dates.DateTime(val) # The next methods fix serialization of UUIDs. The underlying type of a UUID is a UInt128. # JSON tries to encode this as a number in JSON. Encoding integers greater than can # be stored in a signed 64-bit integer sometimes does not work - at least when using -# JSON3. The number gets converted to a float in scientific notation, and so +# JSON. The number gets converted to a float in scientific notation, and so # the UUID is truncated and essentially lost. These functions cause JSON to encode UUIDs as # strings and then convert them back during deserialization. @@ -298,14 +298,14 @@ function serialize_julia_info() end """ -Perform a test to see if JSON3 can convert this value so that the code can give the user a +Perform a test to see if JSON can convert this value so that the code can give the user a a comprehensible corrective action. """ function is_ext_valid_for_serialization(value) isnothing(value) && return true is_valid = true try - JSON3.write(value) + JSON.json(value) catch is_valid = false end diff --git a/src/time_series_metadata_store.jl b/src/time_series_metadata_store.jl index 73d429601..7ad76da49 100644 --- a/src/time_series_metadata_store.jl +++ b/src/time_series_metadata_store.jl @@ -123,7 +123,7 @@ function _load_metadata_into_memory!(store::TimeSeriesMetadataStore) elseif field == :time_series_uuid data[field] = Base.UUID(val) elseif field == :features - features_array = JSON3.read(val, Array) + features_array = JSON.parse(val; dicttype = Dict{String, Any}) features_dict = Dict{String, Union{Bool, Int, String}}() for obj in features_array length(obj) != 1 && error("Invalid features: $obj") @@ -134,7 +134,7 @@ function _load_metadata_into_memory!(store::TimeSeriesMetadataStore) data[field] = features_dict elseif field == :scaling_factor_multiplier if !ismissing(val) - val2 = JSON3.read(val, Dict{String, Any}) + val2 = JSON.parse(val; dicttype = Dict{String, Any}) data[field] = deserialize(Function, val2) end else @@ -294,7 +294,7 @@ function _create_migrated_row(metadata::SingleTimeSeriesMetadata, row) row.owner_type, row.owner_category, row.features, - isnothing(sfm) ? missing : JSON3.write(serialize(sfm)), + isnothing(sfm) ? missing : JSON.json(serialize(sfm)), get_uuid(metadata), missing, ) @@ -317,7 +317,7 @@ function _create_migrated_row(metadata::ForecastMetadata, row) row.owner_type, row.owner_category, row.features, - isnothing(sfm) ? missing : JSON3.write(serialize(sfm)), + isnothing(sfm) ? missing : JSON.json(serialize(sfm)), get_uuid(metadata), missing, ) @@ -503,7 +503,7 @@ function add_metadata!( owner_category, _convert_ts_type_to_string(time_series_type), features, - isnothing(sfm) ? missing : JSON3.write(serialize(sfm)), + isnothing(sfm) ? missing : JSON.json(serialize(sfm)), ) params = chop(repeat("?,", length(vals))) _execute_cached( @@ -1612,7 +1612,7 @@ _convert_ts_type_to_string(::Type{<:Probabilistic}) = _PROBABILISTIC_AS_STRING _convert_ts_type_to_string(::Type{<:Scenarios}) = _SCENARIOS_AS_STRING function _deserialize_metadata(text::String) - val = JSON3.read(text, Dict) + val = JSON.parse(text; dicttype = Dict{String, Any}) return deserialize(get_type_from_serialization_data(val), val) end diff --git a/src/time_series_parser.jl b/src/time_series_parser.jl index a248fb9a0..e827ffab4 100644 --- a/src/time_series_parser.jl +++ b/src/time_series_parser.jl @@ -76,7 +76,7 @@ function read_time_series_file_metadata(file_path::AbstractString) if endswith(file_path, ".json") metadata = open(file_path) do io metadata = Vector{TimeSeriesFileMetadata}() - data = JSON3.read(io, Array) + data = JSON.parse(io; dicttype = Dict{String, Any}) for item in data parsed_resolution = Dates.Millisecond(Dates.Second(item["resolution"])) normalization_factor = item["normalization_factor"] diff --git a/src/utils/generate_struct_files.jl b/src/utils/generate_struct_files.jl index 64f1b625d..7a6c568f7 100644 --- a/src/utils/generate_struct_files.jl +++ b/src/utils/generate_struct_files.jl @@ -147,10 +147,6 @@ function StructDefinition(; return StructDefinition(struct_name, fields, supertype, docstring) end -# These allow JSON3 serialization of the structs. -StructTypes.StructType(::Type{StructDefinition}) = StructTypes.Struct() -StructTypes.StructType(::Type{StructField}) = StructTypes.Struct() - """ Generate a Julia source code file for one struct from a `StructDefinition`. @@ -204,7 +200,7 @@ function generate_struct_files(definitions; filename = nothing, output_directory end data = open(filename, "r") do io - JSON3.read(io, Dict) + JSON.parse(io; dicttype = Dict{String, Any}) end # The user might run this multiple times and so we need to remove existing entries. @@ -224,7 +220,7 @@ function generate_struct_files(definitions; filename = nothing, output_directory end open(filename, "w") do io - JSON3.pretty(io, data, JSON3.AlignmentContext(; indent = 2)) + JSON.json(io, data; pretty = 2) end @info "Added $(length(definitions)) structs to $filename" diff --git a/src/utils/generate_structs.jl b/src/utils/generate_structs.jl index 5dd0552d5..63e1156f9 100644 --- a/src/utils/generate_structs.jl +++ b/src/utils/generate_structs.jl @@ -79,7 +79,7 @@ end function read_json_data(filename::String) return open(filename) do io - data = JSON3.read(io, Dict) + data = JSON.parse(io; dicttype = Dict{String, Any}) if data isa Array return data elseif data isa Dict && haskey(data, "auto_generated_structs") diff --git a/src/utils/recorder_events.jl b/src/utils/recorder_events.jl index aae2827a7..14189f84d 100644 --- a/src/utils/recorder_events.jl +++ b/src/utils/recorder_events.jl @@ -34,9 +34,9 @@ function serialize(event::T) where {T <: AbstractRecorderEvent} return data end -to_json(event::AbstractRecorderEvent) = JSON3.write(serialize(event)) +to_json(event::AbstractRecorderEvent) = JSON.json(serialize(event)) from_json(::Type{T}, text::AbstractString) where {T <: AbstractRecorderEvent} = - deserialize(T, JSON3.read(text, Dict)) + deserialize(T, JSON.parse(text; dicttype = Dict{String, Any})) function deserialize(::Type{T}, data::Dict) where {T <: AbstractRecorderEvent} name = pop!(data, "name") @@ -151,7 +151,7 @@ end function _record_event(name::Symbol, event::AbstractRecorderEvent) # Key is not checked. Callers must use @record and not call this directly. recorder = g_recorders[name] - write(recorder.io, JSON3.write(serialize(event))) + write(recorder.io, JSON.json(serialize(event))) return write(recorder.io, "\n") end diff --git a/src/validation.jl b/src/validation.jl index d423da3c3..e465c73bc 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -16,7 +16,7 @@ function read_validation_descriptor(filename::AbstractString) end elseif occursin(r"(\.json)"i, filename) data = open(filename) do file - return JSON3.read(file, Dict) + return JSON.parse(file; dicttype = Dict{String, Any}) end else error("Filename is not a YAML or JSON file.") From d7ee6cc75dfa56537e74e714ab251dd4592e4a8b Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:44:03 -0600 Subject: [PATCH 03/13] change imports --- src/InfrastructureSystems.jl | 3 +-- src/utils/test.jl | 2 +- src/utils/utils.jl | 9 +++------ test/InfrastructureSystemsTests.jl | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/InfrastructureSystems.jl b/src/InfrastructureSystems.jl index bbf1fa251..1c9428226 100644 --- a/src/InfrastructureSystems.jl +++ b/src/InfrastructureSystems.jl @@ -13,7 +13,7 @@ import CSV import DataFrames import DataFrames: DataFrame import Dates -import JSON3 +import JSON import Logging import Random import Pkg @@ -21,7 +21,6 @@ import PrettyTables import Printf: @sprintf import SHA import StringTemplates -import StructTypes import TerminalLoggers: TerminalLogger, ProgressLevel import TimeSeries import TimerOutputs diff --git a/src/utils/test.jl b/src/utils/test.jl index 72e4bfba4..d35b0fa5e 100644 --- a/src/utils/test.jl +++ b/src/utils/test.jl @@ -51,7 +51,7 @@ supports_time_series(::AdditionalTestComponent) = true supports_time_series(::SimpleTestComponent) = false function from_json(io::IO, ::Type{TestComponent}) - data = JSON3.read(io, Dict) + data = JSON.parse(io; dicttype = Dict{String, Any}) return deserialize(TestComponent, data) end diff --git a/src/utils/utils.jl b/src/utils/utils.jl index 8a485a89d..ef4457f5b 100644 --- a/src/utils/utils.jl +++ b/src/utils/utils.jl @@ -1,6 +1,6 @@ import InteractiveUtils import SHA -import JSON3 +import JSON const HASH_FILENAME = "check.sha256" const COMPARE_VALUES_SENTINEL = :(!NOUPGRADE) # A Symbol that can't be a field name @@ -364,7 +364,6 @@ macro scoped_enum(T, args...) blk = esc( :( module $(Symbol("$(T)Module")) - using JSON3 import InfrastructureSystems export $T struct $T @@ -393,11 +392,9 @@ macro scoped_enum(T, args...) Base.show(io::IO, e::$T) = print(io, string($T, ".", string(e), " = ", e.value)) Base.propertynames(::Type{$T}) = _ALL_NAMES - JSON3.StructType(::Type{$T}) = JSON3.StructTypes.StringType() InfrastructureSystems.serialize(val::$T) = Base.string(val) - InfrastructureSystems.deserialize(::Type{$T}, val) = - JSON3.StructTypes.constructfrom($T, val) + InfrastructureSystems.deserialize(::Type{$T}, val) = $T(val) Base.convert(::Type{$T}, val::Integer) = $T(val) Base.isless(val::$T, other::$T) = isless(val.value, other.value) @@ -726,7 +723,7 @@ function compute_file_hash(path::String, files::Vector{String}) end open(joinpath(path, HASH_FILENAME), "w") do io - JSON3.write(io, data) + JSON.json(io, data) end end diff --git a/test/InfrastructureSystemsTests.jl b/test/InfrastructureSystemsTests.jl index 54ea69a6a..98ad5afe0 100644 --- a/test/InfrastructureSystemsTests.jl +++ b/test/InfrastructureSystemsTests.jl @@ -6,7 +6,7 @@ using Dates import TerminalLoggers: TerminalLogger import TimeSeries import UUIDs -import JSON3 +import JSON import HDF5 using DataStructures: SortedDict using DataFrames From 0ece622345e231daac5e6d6440c79e962a2e98cf Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:44:10 -0600 Subject: [PATCH 04/13] update testing --- test/Project.toml | 2 +- test/test_function_data.jl | 6 +++++- test/test_generate_structs.jl | 2 +- test/test_recorder.jl | 2 +- test/test_serialization.jl | 19 ++++++++++++++----- test/test_time_series.jl | 4 ++-- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index 8015a777c..e11f25865 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,7 +8,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/test/test_function_data.jl b/test/test_function_data.jl index e6a1bf444..4cf8c1576 100644 --- a/test/test_function_data.jl +++ b/test/test_function_data.jl @@ -613,7 +613,11 @@ end for fd in get_test_function_data() for do_jsonify in (false, true) serialized = IS.serialize(fd) - do_jsonify && (serialized = JSON3.read(JSON3.write(serialized), Dict)) + do_jsonify && + ( + serialized = + JSON.parse(JSON.json(serialized); dicttype = Dict{String, Any}) + ) @test typeof(serialized) <: AbstractDict deserialized = IS.deserialize(typeof(fd), serialized) @test deserialized == fd diff --git a/test/test_generate_structs.jl b/test/test_generate_structs.jl index 169669da1..220209aa7 100644 --- a/test/test_generate_structs.jl +++ b/test/test_generate_structs.jl @@ -30,7 +30,7 @@ end ) end data = open(descriptor_file, "r") do io - JSON3.read(io, Dict) + JSON.parse(io; dicttype = Dict{String, Any}) end @test data["auto_generated_structs"][end]["struct_name"] == "MyComponent" diff --git a/test/test_recorder.jl b/test/test_recorder.jl index 018e00c80..6318b9072 100644 --- a/test/test_recorder.jl +++ b/test/test_recorder.jl @@ -12,7 +12,7 @@ @test isfile(filename) lines = readlines(filename) @test length(lines) == 1 - data = JSON3.read(lines[1], Dict) + data = JSON.parse(lines[1]; dicttype = Dict{String, Any}) @test data["name"] == "TestEvent" @test data["val1"] == "a" @test data["val2"] == 1 diff --git a/test/test_serialization.jl b/test/test_serialization.jl index b6fcc8810..3b371d66e 100644 --- a/test/test_serialization.jl +++ b/test/test_serialization.jl @@ -4,7 +4,7 @@ function validate_serialization(sys::IS.SystemData; time_series_read_only = fals IS.prepare_for_serialization_to_file!(sys, filename; force = true) data = IS.serialize(sys) open(filename, "w") do io - JSON3.write(io, data) + JSON.json(io, data) end # Make sure the code supports the files changing directories. @@ -23,7 +23,7 @@ function validate_serialization(sys::IS.SystemData; time_series_read_only = fals end data = open(path) do io - return JSON3.read(io, Dict) + return JSON.parse(io; dicttype = Dict{String, Any}) end orig = pwd() @@ -159,7 +159,10 @@ end @testset "Test JSON string" begin component = IS.SimpleTestComponent("Component1", 1) text = IS.to_json(component) - IS.deserialize(IS.SimpleTestComponent, JSON3.read(text, Dict)) == component + IS.deserialize( + IS.SimpleTestComponent, + JSON.parse(text; dicttype = Dict{String, Any}), + ) == component end @testset "Test pretty-print JSON IO" begin @@ -168,13 +171,19 @@ end IS.to_json(io, component; pretty = false) text = String(take!(io)) @test !occursin(" ", text) - IS.deserialize(IS.SimpleTestComponent, JSON3.read(text, Dict)) == component + IS.deserialize( + IS.SimpleTestComponent, + JSON.parse(text; dicttype = Dict{String, Any}), + ) == component io = IOBuffer() IS.to_json(io, component; pretty = true) text = String(take!(io)) @test occursin(" ", text) - IS.deserialize(IS.SimpleTestComponent, JSON3.read(text, Dict)) == component + IS.deserialize( + IS.SimpleTestComponent, + JSON.parse(text; dicttype = Dict{String, Any}), + ) == component end @testset "Test ext serialization" begin diff --git a/test/test_time_series.jl b/test/test_time_series.jl index 3d0f1ef22..65c61c45b 100644 --- a/test/test_time_series.jl +++ b/test/test_time_series.jl @@ -3922,7 +3922,7 @@ function _setup_for_migration_tests_from_IS_v2_3() string(nameof(typeof(component))), owner_category, features, - JSON3.write(IS.serialize(metadata)), + JSON.json(IS.serialize(metadata)), ) SQLite.DBInterface.execute(stmt, params) end @@ -3948,7 +3948,7 @@ function _setup_for_migration_tests_from_IS_v2_4() ( missing, string(IS.get_uuid(metadata)), - JSON3.write(IS.serialize(metadata)), + JSON.json(IS.serialize(metadata)), ) SQLite.DBInterface.execute(stmt, params) end From 1d893ff4a540ca83e043e424925911f5c165bb38 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 17:00:15 -0600 Subject: [PATCH 05/13] remove support for function deserialization in Ext --- src/serialization.jl | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/serialization.jl b/src/serialization.jl index 1fb0777d7..5f50e211d 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -302,20 +302,39 @@ Perform a test to see if JSON can convert this value so that the code can give t a comprehensible corrective action. """ function is_ext_valid_for_serialization(value) - isnothing(value) && return true - is_valid = true + is_valid = _is_ext_value_basic(value) + if !is_valid + @error "Failed to serialize an 'ext' value. Please ensure that the " * + "contents follow the rules provided in the documentation. Generally, only " * + "basic types are allowed - strings and numbers and arrays, dictionaries, and " * + "structs of those." value + return false + end try JSON.json(value) catch - is_valid = false - end - - if !is_valid @error "Failed to serialize an 'ext' value. Please ensure that the " * "contents follow the rules provided in the documentation. Generally, only " * "basic types are allowed - strings and numbers and arrays, dictionaries, and " * "structs of those." value + return false end + return true +end - return is_valid +# JSON.jl will happily serialize Functions, Modules, Tasks, IO handles, etc. by +# introspecting their fields, but those values are not meaningful JSON content and +# cannot be reliably deserialized. Restrict 'ext' values to genuine data types. +_is_ext_value_basic(::Union{Nothing, Missing, Number, AbstractString, Symbol, Bool}) = true +_is_ext_value_basic(x::AbstractArray) = all(_is_ext_value_basic, x) +_is_ext_value_basic(x::Tuple) = all(_is_ext_value_basic, x) +_is_ext_value_basic(x::AbstractDict) = + all(_is_ext_value_basic, keys(x)) && all(_is_ext_value_basic, values(x)) +_is_ext_value_basic(::Union{Function, Module, Task, IO, Ptr, Base.RefValue}) = false +function _is_ext_value_basic(x::T) where {T} + isstructtype(T) || return false + for name in fieldnames(T) + _is_ext_value_basic(getfield(x, name)) || return false + end + return true end From eda5359f52c9b79ba77a951a3a11b8cd7bab4209 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 22 Apr 2026 00:00:11 -0700 Subject: [PATCH 06/13] update serialize/deserialize code for the scoped enums --- src/utils/utils.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/utils.jl b/src/utils/utils.jl index ef4457f5b..b37b6f656 100644 --- a/src/utils/utils.jl +++ b/src/utils/utils.jl @@ -394,7 +394,11 @@ macro scoped_enum(T, args...) Base.propertynames(::Type{$T}) = _ALL_NAMES InfrastructureSystems.serialize(val::$T) = Base.string(val) + InfrastructureSystems.serialize(vals::Vector{$T}) = + InfrastructureSystems.serialize.(vals) InfrastructureSystems.deserialize(::Type{$T}, val) = $T(val) + InfrastructureSystems.deserialize(::Type{Vector{$T}}, vals::Vector) = + [InfrastructureSystems.deserialize($T, v) for v in vals] Base.convert(::Type{$T}, val::Integer) = $T(val) Base.isless(val::$T, other::$T) = isless(val.value, other.value) From 0327bc996a370b318455fd1fefa7776e90e367f2 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:42:37 -0600 Subject: [PATCH 07/13] drop json3 dependency --- Project.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index e0854dcaf..721adf01e 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,7 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" H5Zblosc = "c8ec2601-a99c-407f-b158-e79c03c2f5f7" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" @@ -24,7 +24,6 @@ SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" StringTemplates = "59c22e0c-4e04-425f-9782-11a552c58a8d" -StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" TerminalLoggers = "5d786b92-1e48-4d6f-9151-6b4477ca9bed" @@ -42,7 +41,7 @@ DocStringExtensions = "0.8, 0.9" H5Zblosc = "0.1" HDF5 = "0.17" InteractiveUtils = "1" -JSON3 = "^1.11" +JSON = "^1.5" LinearAlgebra = "1" Logging = "1" Mustache = "1" @@ -54,7 +53,6 @@ SHA = "0.7" SQLite = "^1.6" Serialization = "1" StringTemplates = "0.1.0" -StructTypes = "^1.9" TOML = "1" Tables = "^1.11" TerminalLoggers = "~0.1" From 6a935fd769751faa0680c576551af760c6e9a221 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:43:32 -0600 Subject: [PATCH 08/13] change API --- src/abstract_time_series.jl | 4 ++-- src/serialization.jl | 16 ++++++++-------- src/time_series_metadata_store.jl | 12 ++++++------ src/time_series_parser.jl | 2 +- src/utils/generate_struct_files.jl | 8 ++------ src/utils/generate_structs.jl | 2 +- src/utils/recorder_events.jl | 6 +++--- src/validation.jl | 2 +- 8 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/abstract_time_series.jl b/src/abstract_time_series.jl index 4a6029d4f..94fa2d478 100644 --- a/src/abstract_time_series.jl +++ b/src/abstract_time_series.jl @@ -17,13 +17,13 @@ end function make_features_string(features::Dict{String, Union{Bool, Int, String}}) key_names = sort!(collect(keys(features))) data = [Dict(k => features[k]) for k in key_names] - return JSON3.write(data) + return JSON.json(data) end function make_features_string(; features...) key_names = sort!(collect(string.(keys(features)))) data = [Dict(k => features[Symbol(k)]) for (k) in key_names] - return JSON3.write(data) + return JSON.json(data) end abstract type ForecastMetadata <: TimeSeriesMetadata end diff --git a/src/serialization.jl b/src/serialization.jl index 8afccc317..1fb0777d7 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -33,10 +33,10 @@ function to_json(obj::T; pretty = false, indent = 2) where {T <: InfrastructureS try if pretty io = IOBuffer() - JSON3.pretty(io, serialize(obj), JSON3.AlignmentContext(; indent = indent)) + JSON.json(io, serialize(obj); pretty = indent) return take!(io) else - return JSON3.write(serialize(obj)) + return JSON.json(serialize(obj)) end catch e @error "Failed to serialize $(summary(obj))" @@ -52,9 +52,9 @@ function to_json( ) where {T <: InfrastructureSystemsType} data = serialize(obj) if pretty - res = JSON3.pretty(io, data, JSON3.AlignmentContext(; indent = indent)) + res = JSON.json(io, data; pretty = indent) else - res = JSON3.write(io, data) + res = JSON.json(io, data) end return res @@ -73,7 +73,7 @@ end Deserializes a InfrastructureSystemsType from String or IO. """ function from_json(io::Union{IO, String}, ::Type{T}) where {T <: InfrastructureSystemsType} - return deserialize(T, JSON3.read(io, Dict)) + return deserialize(T, JSON.parse(io; dicttype = Dict{String, Any})) end """ @@ -271,7 +271,7 @@ deserialize(::Type{Dates.DateTime}, val::AbstractString) = Dates.DateTime(val) # The next methods fix serialization of UUIDs. The underlying type of a UUID is a UInt128. # JSON tries to encode this as a number in JSON. Encoding integers greater than can # be stored in a signed 64-bit integer sometimes does not work - at least when using -# JSON3. The number gets converted to a float in scientific notation, and so +# JSON. The number gets converted to a float in scientific notation, and so # the UUID is truncated and essentially lost. These functions cause JSON to encode UUIDs as # strings and then convert them back during deserialization. @@ -298,14 +298,14 @@ function serialize_julia_info() end """ -Perform a test to see if JSON3 can convert this value so that the code can give the user a +Perform a test to see if JSON can convert this value so that the code can give the user a a comprehensible corrective action. """ function is_ext_valid_for_serialization(value) isnothing(value) && return true is_valid = true try - JSON3.write(value) + JSON.json(value) catch is_valid = false end diff --git a/src/time_series_metadata_store.jl b/src/time_series_metadata_store.jl index 73d429601..7ad76da49 100644 --- a/src/time_series_metadata_store.jl +++ b/src/time_series_metadata_store.jl @@ -123,7 +123,7 @@ function _load_metadata_into_memory!(store::TimeSeriesMetadataStore) elseif field == :time_series_uuid data[field] = Base.UUID(val) elseif field == :features - features_array = JSON3.read(val, Array) + features_array = JSON.parse(val; dicttype = Dict{String, Any}) features_dict = Dict{String, Union{Bool, Int, String}}() for obj in features_array length(obj) != 1 && error("Invalid features: $obj") @@ -134,7 +134,7 @@ function _load_metadata_into_memory!(store::TimeSeriesMetadataStore) data[field] = features_dict elseif field == :scaling_factor_multiplier if !ismissing(val) - val2 = JSON3.read(val, Dict{String, Any}) + val2 = JSON.parse(val; dicttype = Dict{String, Any}) data[field] = deserialize(Function, val2) end else @@ -294,7 +294,7 @@ function _create_migrated_row(metadata::SingleTimeSeriesMetadata, row) row.owner_type, row.owner_category, row.features, - isnothing(sfm) ? missing : JSON3.write(serialize(sfm)), + isnothing(sfm) ? missing : JSON.json(serialize(sfm)), get_uuid(metadata), missing, ) @@ -317,7 +317,7 @@ function _create_migrated_row(metadata::ForecastMetadata, row) row.owner_type, row.owner_category, row.features, - isnothing(sfm) ? missing : JSON3.write(serialize(sfm)), + isnothing(sfm) ? missing : JSON.json(serialize(sfm)), get_uuid(metadata), missing, ) @@ -503,7 +503,7 @@ function add_metadata!( owner_category, _convert_ts_type_to_string(time_series_type), features, - isnothing(sfm) ? missing : JSON3.write(serialize(sfm)), + isnothing(sfm) ? missing : JSON.json(serialize(sfm)), ) params = chop(repeat("?,", length(vals))) _execute_cached( @@ -1612,7 +1612,7 @@ _convert_ts_type_to_string(::Type{<:Probabilistic}) = _PROBABILISTIC_AS_STRING _convert_ts_type_to_string(::Type{<:Scenarios}) = _SCENARIOS_AS_STRING function _deserialize_metadata(text::String) - val = JSON3.read(text, Dict) + val = JSON.parse(text; dicttype = Dict{String, Any}) return deserialize(get_type_from_serialization_data(val), val) end diff --git a/src/time_series_parser.jl b/src/time_series_parser.jl index a248fb9a0..e827ffab4 100644 --- a/src/time_series_parser.jl +++ b/src/time_series_parser.jl @@ -76,7 +76,7 @@ function read_time_series_file_metadata(file_path::AbstractString) if endswith(file_path, ".json") metadata = open(file_path) do io metadata = Vector{TimeSeriesFileMetadata}() - data = JSON3.read(io, Array) + data = JSON.parse(io; dicttype = Dict{String, Any}) for item in data parsed_resolution = Dates.Millisecond(Dates.Second(item["resolution"])) normalization_factor = item["normalization_factor"] diff --git a/src/utils/generate_struct_files.jl b/src/utils/generate_struct_files.jl index 64f1b625d..7a6c568f7 100644 --- a/src/utils/generate_struct_files.jl +++ b/src/utils/generate_struct_files.jl @@ -147,10 +147,6 @@ function StructDefinition(; return StructDefinition(struct_name, fields, supertype, docstring) end -# These allow JSON3 serialization of the structs. -StructTypes.StructType(::Type{StructDefinition}) = StructTypes.Struct() -StructTypes.StructType(::Type{StructField}) = StructTypes.Struct() - """ Generate a Julia source code file for one struct from a `StructDefinition`. @@ -204,7 +200,7 @@ function generate_struct_files(definitions; filename = nothing, output_directory end data = open(filename, "r") do io - JSON3.read(io, Dict) + JSON.parse(io; dicttype = Dict{String, Any}) end # The user might run this multiple times and so we need to remove existing entries. @@ -224,7 +220,7 @@ function generate_struct_files(definitions; filename = nothing, output_directory end open(filename, "w") do io - JSON3.pretty(io, data, JSON3.AlignmentContext(; indent = 2)) + JSON.json(io, data; pretty = 2) end @info "Added $(length(definitions)) structs to $filename" diff --git a/src/utils/generate_structs.jl b/src/utils/generate_structs.jl index 5dd0552d5..63e1156f9 100644 --- a/src/utils/generate_structs.jl +++ b/src/utils/generate_structs.jl @@ -79,7 +79,7 @@ end function read_json_data(filename::String) return open(filename) do io - data = JSON3.read(io, Dict) + data = JSON.parse(io; dicttype = Dict{String, Any}) if data isa Array return data elseif data isa Dict && haskey(data, "auto_generated_structs") diff --git a/src/utils/recorder_events.jl b/src/utils/recorder_events.jl index aae2827a7..14189f84d 100644 --- a/src/utils/recorder_events.jl +++ b/src/utils/recorder_events.jl @@ -34,9 +34,9 @@ function serialize(event::T) where {T <: AbstractRecorderEvent} return data end -to_json(event::AbstractRecorderEvent) = JSON3.write(serialize(event)) +to_json(event::AbstractRecorderEvent) = JSON.json(serialize(event)) from_json(::Type{T}, text::AbstractString) where {T <: AbstractRecorderEvent} = - deserialize(T, JSON3.read(text, Dict)) + deserialize(T, JSON.parse(text; dicttype = Dict{String, Any})) function deserialize(::Type{T}, data::Dict) where {T <: AbstractRecorderEvent} name = pop!(data, "name") @@ -151,7 +151,7 @@ end function _record_event(name::Symbol, event::AbstractRecorderEvent) # Key is not checked. Callers must use @record and not call this directly. recorder = g_recorders[name] - write(recorder.io, JSON3.write(serialize(event))) + write(recorder.io, JSON.json(serialize(event))) return write(recorder.io, "\n") end diff --git a/src/validation.jl b/src/validation.jl index d423da3c3..e465c73bc 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -16,7 +16,7 @@ function read_validation_descriptor(filename::AbstractString) end elseif occursin(r"(\.json)"i, filename) data = open(filename) do file - return JSON3.read(file, Dict) + return JSON.parse(file; dicttype = Dict{String, Any}) end else error("Filename is not a YAML or JSON file.") From d20824cad8519075d4c4b5934d49e3659c378dae Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:44:03 -0600 Subject: [PATCH 09/13] change imports --- src/InfrastructureSystems.jl | 3 +-- src/utils/test.jl | 2 +- src/utils/utils.jl | 9 +++------ test/InfrastructureSystemsTests.jl | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/InfrastructureSystems.jl b/src/InfrastructureSystems.jl index bbf1fa251..1c9428226 100644 --- a/src/InfrastructureSystems.jl +++ b/src/InfrastructureSystems.jl @@ -13,7 +13,7 @@ import CSV import DataFrames import DataFrames: DataFrame import Dates -import JSON3 +import JSON import Logging import Random import Pkg @@ -21,7 +21,6 @@ import PrettyTables import Printf: @sprintf import SHA import StringTemplates -import StructTypes import TerminalLoggers: TerminalLogger, ProgressLevel import TimeSeries import TimerOutputs diff --git a/src/utils/test.jl b/src/utils/test.jl index 72e4bfba4..d35b0fa5e 100644 --- a/src/utils/test.jl +++ b/src/utils/test.jl @@ -51,7 +51,7 @@ supports_time_series(::AdditionalTestComponent) = true supports_time_series(::SimpleTestComponent) = false function from_json(io::IO, ::Type{TestComponent}) - data = JSON3.read(io, Dict) + data = JSON.parse(io; dicttype = Dict{String, Any}) return deserialize(TestComponent, data) end diff --git a/src/utils/utils.jl b/src/utils/utils.jl index 8a485a89d..ef4457f5b 100644 --- a/src/utils/utils.jl +++ b/src/utils/utils.jl @@ -1,6 +1,6 @@ import InteractiveUtils import SHA -import JSON3 +import JSON const HASH_FILENAME = "check.sha256" const COMPARE_VALUES_SENTINEL = :(!NOUPGRADE) # A Symbol that can't be a field name @@ -364,7 +364,6 @@ macro scoped_enum(T, args...) blk = esc( :( module $(Symbol("$(T)Module")) - using JSON3 import InfrastructureSystems export $T struct $T @@ -393,11 +392,9 @@ macro scoped_enum(T, args...) Base.show(io::IO, e::$T) = print(io, string($T, ".", string(e), " = ", e.value)) Base.propertynames(::Type{$T}) = _ALL_NAMES - JSON3.StructType(::Type{$T}) = JSON3.StructTypes.StringType() InfrastructureSystems.serialize(val::$T) = Base.string(val) - InfrastructureSystems.deserialize(::Type{$T}, val) = - JSON3.StructTypes.constructfrom($T, val) + InfrastructureSystems.deserialize(::Type{$T}, val) = $T(val) Base.convert(::Type{$T}, val::Integer) = $T(val) Base.isless(val::$T, other::$T) = isless(val.value, other.value) @@ -726,7 +723,7 @@ function compute_file_hash(path::String, files::Vector{String}) end open(joinpath(path, HASH_FILENAME), "w") do io - JSON3.write(io, data) + JSON.json(io, data) end end diff --git a/test/InfrastructureSystemsTests.jl b/test/InfrastructureSystemsTests.jl index 54ea69a6a..98ad5afe0 100644 --- a/test/InfrastructureSystemsTests.jl +++ b/test/InfrastructureSystemsTests.jl @@ -6,7 +6,7 @@ using Dates import TerminalLoggers: TerminalLogger import TimeSeries import UUIDs -import JSON3 +import JSON import HDF5 using DataStructures: SortedDict using DataFrames From cad71dc0267139500e3fc9d5cbac0281473347b2 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 16:44:10 -0600 Subject: [PATCH 10/13] update testing --- test/Project.toml | 2 +- test/test_function_data.jl | 6 +++++- test/test_generate_structs.jl | 2 +- test/test_recorder.jl | 2 +- test/test_serialization.jl | 19 ++++++++++++++----- test/test_time_series.jl | 4 ++-- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index 8015a777c..e11f25865 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,7 +8,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/test/test_function_data.jl b/test/test_function_data.jl index e6a1bf444..4cf8c1576 100644 --- a/test/test_function_data.jl +++ b/test/test_function_data.jl @@ -613,7 +613,11 @@ end for fd in get_test_function_data() for do_jsonify in (false, true) serialized = IS.serialize(fd) - do_jsonify && (serialized = JSON3.read(JSON3.write(serialized), Dict)) + do_jsonify && + ( + serialized = + JSON.parse(JSON.json(serialized); dicttype = Dict{String, Any}) + ) @test typeof(serialized) <: AbstractDict deserialized = IS.deserialize(typeof(fd), serialized) @test deserialized == fd diff --git a/test/test_generate_structs.jl b/test/test_generate_structs.jl index 169669da1..220209aa7 100644 --- a/test/test_generate_structs.jl +++ b/test/test_generate_structs.jl @@ -30,7 +30,7 @@ end ) end data = open(descriptor_file, "r") do io - JSON3.read(io, Dict) + JSON.parse(io; dicttype = Dict{String, Any}) end @test data["auto_generated_structs"][end]["struct_name"] == "MyComponent" diff --git a/test/test_recorder.jl b/test/test_recorder.jl index 018e00c80..6318b9072 100644 --- a/test/test_recorder.jl +++ b/test/test_recorder.jl @@ -12,7 +12,7 @@ @test isfile(filename) lines = readlines(filename) @test length(lines) == 1 - data = JSON3.read(lines[1], Dict) + data = JSON.parse(lines[1]; dicttype = Dict{String, Any}) @test data["name"] == "TestEvent" @test data["val1"] == "a" @test data["val2"] == 1 diff --git a/test/test_serialization.jl b/test/test_serialization.jl index b6fcc8810..3b371d66e 100644 --- a/test/test_serialization.jl +++ b/test/test_serialization.jl @@ -4,7 +4,7 @@ function validate_serialization(sys::IS.SystemData; time_series_read_only = fals IS.prepare_for_serialization_to_file!(sys, filename; force = true) data = IS.serialize(sys) open(filename, "w") do io - JSON3.write(io, data) + JSON.json(io, data) end # Make sure the code supports the files changing directories. @@ -23,7 +23,7 @@ function validate_serialization(sys::IS.SystemData; time_series_read_only = fals end data = open(path) do io - return JSON3.read(io, Dict) + return JSON.parse(io; dicttype = Dict{String, Any}) end orig = pwd() @@ -159,7 +159,10 @@ end @testset "Test JSON string" begin component = IS.SimpleTestComponent("Component1", 1) text = IS.to_json(component) - IS.deserialize(IS.SimpleTestComponent, JSON3.read(text, Dict)) == component + IS.deserialize( + IS.SimpleTestComponent, + JSON.parse(text; dicttype = Dict{String, Any}), + ) == component end @testset "Test pretty-print JSON IO" begin @@ -168,13 +171,19 @@ end IS.to_json(io, component; pretty = false) text = String(take!(io)) @test !occursin(" ", text) - IS.deserialize(IS.SimpleTestComponent, JSON3.read(text, Dict)) == component + IS.deserialize( + IS.SimpleTestComponent, + JSON.parse(text; dicttype = Dict{String, Any}), + ) == component io = IOBuffer() IS.to_json(io, component; pretty = true) text = String(take!(io)) @test occursin(" ", text) - IS.deserialize(IS.SimpleTestComponent, JSON3.read(text, Dict)) == component + IS.deserialize( + IS.SimpleTestComponent, + JSON.parse(text; dicttype = Dict{String, Any}), + ) == component end @testset "Test ext serialization" begin diff --git a/test/test_time_series.jl b/test/test_time_series.jl index 4a4623fcf..9239bf0cf 100644 --- a/test/test_time_series.jl +++ b/test/test_time_series.jl @@ -3950,7 +3950,7 @@ function _setup_for_migration_tests_from_IS_v2_3() string(nameof(typeof(component))), owner_category, features, - JSON3.write(IS.serialize(metadata)), + JSON.json(IS.serialize(metadata)), ) SQLite.DBInterface.execute(stmt, params) end @@ -3976,7 +3976,7 @@ function _setup_for_migration_tests_from_IS_v2_4() ( missing, string(IS.get_uuid(metadata)), - JSON3.write(IS.serialize(metadata)), + JSON.json(IS.serialize(metadata)), ) SQLite.DBInterface.execute(stmt, params) end From fbba109a8e7703e527f66f42bcdc7a2b7646d75b Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 15 Apr 2026 17:00:15 -0600 Subject: [PATCH 11/13] remove support for function deserialization in Ext --- src/serialization.jl | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/serialization.jl b/src/serialization.jl index 1fb0777d7..5f50e211d 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -302,20 +302,39 @@ Perform a test to see if JSON can convert this value so that the code can give t a comprehensible corrective action. """ function is_ext_valid_for_serialization(value) - isnothing(value) && return true - is_valid = true + is_valid = _is_ext_value_basic(value) + if !is_valid + @error "Failed to serialize an 'ext' value. Please ensure that the " * + "contents follow the rules provided in the documentation. Generally, only " * + "basic types are allowed - strings and numbers and arrays, dictionaries, and " * + "structs of those." value + return false + end try JSON.json(value) catch - is_valid = false - end - - if !is_valid @error "Failed to serialize an 'ext' value. Please ensure that the " * "contents follow the rules provided in the documentation. Generally, only " * "basic types are allowed - strings and numbers and arrays, dictionaries, and " * "structs of those." value + return false end + return true +end - return is_valid +# JSON.jl will happily serialize Functions, Modules, Tasks, IO handles, etc. by +# introspecting their fields, but those values are not meaningful JSON content and +# cannot be reliably deserialized. Restrict 'ext' values to genuine data types. +_is_ext_value_basic(::Union{Nothing, Missing, Number, AbstractString, Symbol, Bool}) = true +_is_ext_value_basic(x::AbstractArray) = all(_is_ext_value_basic, x) +_is_ext_value_basic(x::Tuple) = all(_is_ext_value_basic, x) +_is_ext_value_basic(x::AbstractDict) = + all(_is_ext_value_basic, keys(x)) && all(_is_ext_value_basic, values(x)) +_is_ext_value_basic(::Union{Function, Module, Task, IO, Ptr, Base.RefValue}) = false +function _is_ext_value_basic(x::T) where {T} + isstructtype(T) || return false + for name in fieldnames(T) + _is_ext_value_basic(getfield(x, name)) || return false + end + return true end From 4b22439afb6a83359a24044a3318b491e0d6d808 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 22 Apr 2026 00:00:11 -0700 Subject: [PATCH 12/13] update serialize/deserialize code for the scoped enums --- src/utils/utils.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/utils.jl b/src/utils/utils.jl index ef4457f5b..b37b6f656 100644 --- a/src/utils/utils.jl +++ b/src/utils/utils.jl @@ -394,7 +394,11 @@ macro scoped_enum(T, args...) Base.propertynames(::Type{$T}) = _ALL_NAMES InfrastructureSystems.serialize(val::$T) = Base.string(val) + InfrastructureSystems.serialize(vals::Vector{$T}) = + InfrastructureSystems.serialize.(vals) InfrastructureSystems.deserialize(::Type{$T}, val) = $T(val) + InfrastructureSystems.deserialize(::Type{Vector{$T}}, vals::Vector) = + [InfrastructureSystems.deserialize($T, v) for v in vals] Base.convert(::Type{$T}, val::Integer) = $T(val) Base.isless(val::$T, other::$T) = isless(val.value, other.value) From 14e304b06c041c07e113c6310ed395d66591c1e1 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 22 Apr 2026 00:35:26 -0700 Subject: [PATCH 13/13] apply recommendation from copilot --- src/serialization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serialization.jl b/src/serialization.jl index 5f50e211d..dc85a5faa 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -34,7 +34,7 @@ function to_json(obj::T; pretty = false, indent = 2) where {T <: InfrastructureS if pretty io = IOBuffer() JSON.json(io, serialize(obj); pretty = indent) - return take!(io) + return String(take!(io)) else return JSON.json(serialize(obj)) end