Skip to content

Commit 7fbfb06

Browse files
committed
feat: distinguish missing and nothing, prefer nothing
1 parent 3d9d086 commit 7fbfb06

File tree

4 files changed

+38
-4
lines changed

4 files changed

+38
-4
lines changed

src/gentypes.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ end
99
struct Top end
1010

1111
# get the type from a named tuple, given a name
12-
get_type(NT, k) = hasfield(NT, k) ? fieldtype(NT, k) : Nothing
12+
get_type(NT, k) = hasfield(NT, k) ? fieldtype(NT, k) : Missing
1313

1414
# unify two types to a single type
1515
function promoteunion(T, S)
@@ -36,7 +36,7 @@ function unify(
3636
for (k, v) in zip(B, fieldtypes(b))
3737
if !(k in ks)
3838
push!(ks, k)
39-
push!(ts, unify(v, Nothing))
39+
push!(ts, unify(v, Missing))
4040
end
4141
end
4242

@@ -161,13 +161,27 @@ function generate_expr!(
161161
mutable::Bool = true,
162162
) where {N,T<:Tuple}
163163
sub_exprs = []
164+
missing_fields = []
164165
for (n, t) in zip(N, fieldtypes(nt))
165166
push!(sub_exprs, generate_field_expr!(exprs, t, n; mutable = mutable))
167+
if Missing <: t
168+
push!(missing_fields, n)
169+
end
166170
end
167171

168172
struct_name = pascalcase(root_name)
173+
missing_kw_args = (length(missing_fields) > 0 ? "; " : "") * join(map(n -> "n=missing", missing_fields), ", ")
169174
if mutable
170-
push!(sub_exprs, Meta.parse("$struct_name() = new()"))
175+
if length(missing_fields) == 0
176+
push!(sub_exprs, Meta.parse("$struct_name() = new()"))
177+
else
178+
missing_assigns = join(map(n -> "x.$n = missing", missing_fields), "\n")
179+
push!(sub_exprs, Meta.parse("""function $struct_name()
180+
x = new()
181+
$missing_assigns
182+
return x
183+
end"""))
184+
end
171185
end
172186

173187
push!(exprs, Expr(:struct, mutable, struct_name, Expr(:block, sub_exprs...)))

src/structs.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ function read(::Struct, buf, pos, len, b, U::Union; kw...)
5252
# Julia implementation detail: Unions are sorted :)
5353
# This lets us avoid the below try-catch when U <: Union{Missing,T}
5454
if U.a === Nothing || U.a === Missing
55-
if buf[pos] == UInt8('n')
55+
# fallback to nothing if Union{Missing, Nothing}
56+
if buf[pos] == UInt8('n') && !(Nothing <: U.b)
5657
return read(StructType(U.a), buf, pos, len, b, U.a)
5758
else
5859
return read(StructType(U.b), buf, pos, len, b, U.b; kw...)

test/gentypes.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,22 @@
313313
@test length(json_arr) == 2
314314
@test json_arr[1].menu.header == "SVG Viewer"
315315
end
316+
317+
@testset "Missingness" begin
318+
json = """
319+
[
320+
{"a": "w", "b": 5, "c": 9, "d": null},
321+
{"a": 3, "b": 4, "c": 2},
322+
{"a": 7, "b": 7, "c": 0, "d": 10}
323+
]
324+
"""
325+
326+
JSON3.@generatetypes(json)
327+
res = JSON3.read(json, Vector{JSONTypes.Root})
328+
329+
@test res[3].d == 10
330+
@test ismissing(res[2].d)
331+
@test res[1].d === nothing
332+
333+
end
316334
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ obj = JSON3.read("""
456456
@test JSON3.read("\"1\"", Union{String, Int}) == "1"
457457
@test JSON3.read("null", Union{Int, String, Nothing}) === nothing
458458
@test JSON3.read("1.0", Union{Float64, Int}) === 1.0
459+
@test JSON3.read("null", Union{Missing, Nothing, Int}) === nothing
459460

460461
@test JSON3.read("1", Any) == 1
461462
@test JSON3.read("3.14", Any) == 3.14

0 commit comments

Comments
 (0)