Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand Down
3 changes: 1 addition & 2 deletions src/InfrastructureSystems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ import CSV
import DataFrames
import DataFrames: DataFrame
import Dates
import JSON3
import JSON
import Logging
import Random
import Pkg
import PrettyTables
import Printf: @sprintf
import SHA
import StringTemplates
import StructTypes
import TerminalLoggers: TerminalLogger, ProgressLevel
import TimeSeries
import TimerOutputs
Expand Down
4 changes: 2 additions & 2 deletions src/abstract_time_series.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 35 additions & 16 deletions src/serialization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
return take!(io)
JSON.json(io, serialize(obj); pretty = indent)
return String(take!(io))
else
return JSON3.write(serialize(obj))
return JSON.json(serialize(obj))
end
catch e
@error "Failed to serialize $(summary(obj))"
Expand All @@ -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
Expand All @@ -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

"""
Expand Down Expand Up @@ -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.

Expand All @@ -298,24 +298,43 @@ 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
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
JSON3.write(value)
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
12 changes: 6 additions & 6 deletions src/time_series_metadata_store.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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,
)
Expand All @@ -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,
)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/time_series_parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
8 changes: 2 additions & 6 deletions src/utils/generate_struct_files.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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.
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/utils/generate_structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 3 additions & 3 deletions src/utils/recorder_events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/utils/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 7 additions & 6 deletions src/utils/utils.jl
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -364,7 +364,6 @@ macro scoped_enum(T, args...)
blk = esc(
:(
module $(Symbol("$(T)Module"))
using JSON3
import InfrastructureSystems
export $T
struct $T
Expand Down Expand Up @@ -393,11 +392,13 @@ 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.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)
Expand Down Expand Up @@ -726,7 +727,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

Expand Down
2 changes: 1 addition & 1 deletion src/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
2 changes: 1 addition & 1 deletion test/InfrastructureSystemsTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ using Dates
import TerminalLoggers: TerminalLogger
import TimeSeries
import UUIDs
import JSON3
import JSON
import HDF5
using DataStructures: SortedDict
using DataFrames
Expand Down
2 changes: 1 addition & 1 deletion test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 5 additions & 1 deletion test/test_function_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/test_generate_structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion test/test_recorder.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading