diff --git a/src/gentypes.jl b/src/gentypes.jl index 48d101c..6a49c35 100644 --- a/src/gentypes.jl +++ b/src/gentypes.jl @@ -9,7 +9,7 @@ end struct Top end # get the type from a named tuple, given a name -get_type(NT, k) = hasfield(NT, k) ? fieldtype(NT, k) : Nothing +get_type(NT, k) = hasfield(NT, k) ? fieldtype(NT, k) : Missing # unify two types to a single type function promoteunion(T, S) @@ -36,7 +36,7 @@ function unify( for (k, v) in zip(B, fieldtypes(b)) if !(k in ks) push!(ks, k) - push!(ts, unify(v, Nothing)) + push!(ts, unify(v, Missing)) end end @@ -161,13 +161,27 @@ function generate_expr!( mutable::Bool = true, ) where {N,T<:Tuple} sub_exprs = [] + missing_fields = [] for (n, t) in zip(N, fieldtypes(nt)) push!(sub_exprs, generate_field_expr!(exprs, t, n; mutable = mutable)) + if Missing <: t + push!(missing_fields, n) + end end struct_name = pascalcase(root_name) + missing_kw_args = (length(missing_fields) > 0 ? "; " : "") * join(map(n -> "n=missing", missing_fields), ", ") if mutable - push!(sub_exprs, Meta.parse("$struct_name() = new()")) + if length(missing_fields) == 0 + push!(sub_exprs, Meta.parse("$struct_name() = new()")) + else + missing_assigns = join(map(n -> "x.$n = missing", missing_fields), "\n") + push!(sub_exprs, Meta.parse("""function $struct_name() + x = new() + $missing_assigns + return x + end""")) + end end push!(exprs, Expr(:struct, mutable, struct_name, Expr(:block, sub_exprs...))) diff --git a/src/structs.jl b/src/structs.jl index bc722ee..7eae3c7 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -52,7 +52,8 @@ function read(::Struct, buf, pos, len, b, U::Union; kw...) # Julia implementation detail: Unions are sorted :) # This lets us avoid the below try-catch when U <: Union{Missing,T} if U.a === Nothing || U.a === Missing - if buf[pos] == UInt8('n') + # fallback to nothing if Union{Missing, Nothing} + if buf[pos] == UInt8('n') && !(Nothing <: U.b) return read(StructType(U.a), buf, pos, len, b, U.a) else return read(StructType(U.b), buf, pos, len, b, U.b; kw...) diff --git a/test/gentypes.jl b/test/gentypes.jl index bb168b2..8e7b683 100644 --- a/test/gentypes.jl +++ b/test/gentypes.jl @@ -313,4 +313,22 @@ @test length(json_arr) == 2 @test json_arr[1].menu.header == "SVG Viewer" end + + @testset "Missingness" begin + json = """ + [ + {"a": "w", "b": 5, "c": 9, "d": null}, + {"a": 3, "b": 4, "c": 2}, + {"a": 7, "b": 7, "c": 0, "d": 10} + ] + """ + + JSON3.@generatetypes(json) + res = JSON3.read(json, Vector{JSONTypes.Root}) + + @test res[3].d == 10 + @test ismissing(res[2].d) + @test res[1].d === nothing + + end end diff --git a/test/runtests.jl b/test/runtests.jl index 42e420c..7ba18ae 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -456,6 +456,7 @@ obj = JSON3.read(""" @test JSON3.read("\"1\"", Union{String, Int}) == "1" @test JSON3.read("null", Union{Int, String, Nothing}) === nothing @test JSON3.read("1.0", Union{Float64, Int}) === 1.0 +@test JSON3.read("null", Union{Missing, Nothing, Int}) === nothing @test JSON3.read("1", Any) == 1 @test JSON3.read("3.14", Any) == 3.14