Skip to content

Commit 8154d24

Browse files
quinnjclaude
andauthored
fix(parse): forward type predicates to inner style (#454)
JSONReadStyle wraps the user-provided style for null/dicttype/unknown_fields config, but StructUtils dispatches on the wrapping JSONReadStyle. As a result, methods like dictlike(::CustomJSONStyle, ::Type{A}) defined on a user JSONStyle never fired during parsing — make() fell through to makestruct instead of makedict. Forward dictlike, arraylike, and nulllike from JSONReadStyle to its inner st.style so user-defined dispatches on a custom JSONStyle reach the StructUtils make() router. Closes #453 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 95024e9 commit 8154d24

2 files changed

Lines changed: 18 additions & 0 deletions

File tree

src/parse.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ nullvalue(st::JSONReadStyle) = st.null
171171
StructUtils.fieldtagkey(::JSONStyle) = :json
172172
StructUtils.defaultstate(st::JSONReadStyle) = StructUtils.defaultstate(st.style)
173173

174+
# forward StructUtils API to the inner style so user-provided JSONStyle dispatches are honored
175+
StructUtils.dictlike(st::JSONReadStyle, ::Type{T}) where {T} = StructUtils.dictlike(st.style, T)
176+
StructUtils.arraylike(st::JSONReadStyle, ::Type{T}) where {T} = StructUtils.arraylike(st.style, T)
177+
StructUtils.nulllike(st::JSONReadStyle, ::Type{T}) where {T} = StructUtils.nulllike(st.style, T)
178+
174179
function jsonreadstyle(::Type{T}, ::Type{O}, null, style::StructStyle, unknown_fields::Symbol) where {T,O}
175180
ignore_unknown_fields =
176181
unknown_fields === :ignore ? true :

test/parse.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ end
227227
any::Any &(choosetype=x -> x.type[] == "int" ? @NamedTuple{type::String, value::Int} : x.type[] == "float" ? @NamedTuple{type::String, value::Float64} : @NamedTuple{type::String, value::String},)
228228
end
229229

230+
# https://github.com/JuliaIO/JSON.jl/issues/453 - custom JSONStyle dictlike dispatch
231+
@kwdef struct DictlikeViaCustomStyle
232+
vals::Dict{String,Int} = Dict{String,Int}()
233+
end
234+
Base.keytype(::DictlikeViaCustomStyle) = String
235+
Base.valtype(::DictlikeViaCustomStyle) = Int
236+
StructUtils.addkeyval!(a::DictlikeViaCustomStyle, k, v) = StructUtils.addkeyval!(a.vals, k, v)
237+
StructUtils.dictlike(::CustomJSONStyle, ::Type{DictlikeViaCustomStyle}) = true
238+
230239
@testset "JSON.parse" begin
231240
@testset "errors" begin
232241
# Unexpected character in array
@@ -765,6 +774,10 @@ end
765774
JSON.lift(::CustomJSONStyle, ::Type{Rational}, x) = Rational(x.num[], x.den[])
766775
@test JSON.parse("{\"num\": 1,\"den\":3}", Rational; style=CustomJSONStyle()) == 1//3
767776
@test JSON.parse("{\"num\": 1,\"den\":3}", Rational; style=CustomJSONStyle(), unknown_fields=:error) == 1//3
777+
# https://github.com/JuliaIO/JSON.jl/issues/453 - dictlike dispatch on custom JSONStyle must reach user method
778+
let res = JSON.parse("""{"a": 1, "b": 2}""", DictlikeViaCustomStyle; style=CustomJSONStyle())
779+
@test res.vals == Dict("a" => 1, "b" => 2)
780+
end
768781
@test isequal(JSON.parse("{\"num\": 1,\"den\":null}", @NamedTuple{num::Int, den::Union{Int, Missing}}; null=missing, style=StructUtils.DefaultStyle()), (num=1, den=missing))
769782
# choosetype field tag on Any struct field
770783
@test JSON.parse("{\"id\":1,\"any\":{\"type\":\"int\",\"value\":10}}", Q) == Q(1, (type="int", value=10))

0 commit comments

Comments
 (0)