I have the following problem migrating from JSON3.jl to JSON.jl v1:
julia> struct MyStruct
a::Int
b::String
MyStruct(a::Int) = new(a, string(a))
end
julia> JSON.parse("""{"a": 42}""", MyStruct)
ERROR: TypeError: in typeassert, expected String, got a value of type Nothing
...
julia> JSON3.read("""{"a": 42}""", MyStruct2) # works out of the box
MyStruct2(42, "42")
I haven’t been able to figure out how to make this work. There is StructUtils.@defaults, which works for construction but not for parsing:
julia> @defaults struct MyStruct2
a::Int
b::String = string(a)
end
julia> MyStruct2(1)
MyStruct2(1, "1")
julia> JSON.parse("""{"a": 42}""", MyStruct2)
ERROR: UndefVarError: `a` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
...
I think JSON.jl wants you to represent such a type with explicit option, a la
struct MyStruct
a::Int
b::Union{String, Nothing, Missing} #nothing: value not set in json; missing: explicit null but key is present
end
If you want to conflate {"a":1} and {"a":1, "b":null} then you can use simple Union{Nothing, String}.
That being said, you should open an issue on StructUtils. The generated default constructors and StructUtils.fielddefaults should ideally have the same behavior on this.
(I fear that this will be not be entirely trivial to get right without breakage, especially side-effects and their ordering of default argument argument evaluation needs to be the same between “default arguments for function / type constructor” and “construction via JSON.parse / StructUtils.fielddefaults”).
julia> @macroexpand @defaults struct A
x::Int
y::Int = x
end
quote
begin
$(Expr(:meta, :doc))
struct A
#= REPL[3]:2 =#
x::Int
#= REPL[3]:3 =#
y::Int
end
end
function A(x)
#= REPL[3]:1 =#
return A(x, x)
end
StructUtils.fielddefaults(::StructUtils.StructStyle, ::Type{<:A}) = begin
y = x
return (; $(Expr(:(=), :y, :y)))
end
A
end