From e269a14bc132045ef63d3a49774b4bd6dfefd5c1 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Tue, 26 May 2026 06:45:20 -0600 Subject: [PATCH] fix(parse): honor custom style structlike Forward JSONReadStyle structlike checks to the wrapped JSONStyle so custom styles can opt foreign types out of struct materialization. Fixes #462 --- src/parse.jl | 6 ++++++ test/parse.jl | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/parse.jl b/src/parse.jl index f541d6b..b4c5a9c 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -177,6 +177,12 @@ StructUtils.defaultstate(st::JSONReadStyle) = StructUtils.defaultstate(st.style) StructUtils.dictlike(st::JSONReadStyle, ::Type{T}) where {T} = StructUtils.dictlike(st.style, T) StructUtils.arraylike(st::JSONReadStyle, ::Type{T}) where {T} = StructUtils.arraylike(st.style, T) StructUtils.nulllike(st::JSONReadStyle, ::Type{T}) where {T} = StructUtils.nulllike(st.style, T) +# Keep structlike forwarding specific to custom JSONStyle wrappers so type-level StructStyle +# specializations (for example @nonstruct types) continue to dispatch without ambiguity. +StructUtils.structlike(st::JSONReadStyle{O,N,S}, ::Type{T}) where {O,N,S<:JSONStyle,T} = + StructUtils.structlike(st.style, T) +StructUtils.structlike(st::JSONReadStyle{O,N,S}, ::Type{T}) where {O,N,S<:JSONStyle,T<:NamedTuple} = + StructUtils.structlike(st.style, T) function jsonreadstyle(::Type{T}, ::Type{O}, null, style::StructStyle, unknown_fields::Symbol) where {T,O} ignore_unknown_fields = diff --git a/test/parse.jl b/test/parse.jl index ac1141e..500fed6 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1,6 +1,7 @@ using JSON, StructUtils, UUIDs, Dates, Test struct CustomJSONStyle <: JSON.JSONStyle end +struct RefValueStyle <: JSON.JSONStyle end struct A a::Int @@ -236,6 +237,10 @@ Base.valtype(::DictlikeViaCustomStyle) = Int StructUtils.addkeyval!(a::DictlikeViaCustomStyle, k, v) = StructUtils.addkeyval!(a.vals, k, v) StructUtils.dictlike(::CustomJSONStyle, ::Type{DictlikeViaCustomStyle}) = true +StructUtils.structlike(::RefValueStyle, ::Type{Base.RefValue{Int}}) = false +StructUtils.lower(::RefValueStyle, x::Base.RefValue{Int}) = x[] +StructUtils.lift(::RefValueStyle, ::Type{Base.RefValue{Int}}, x::Integer) = Ref{Int}(x), nothing + @testset "JSON.parse" begin @testset "errors" begin # Unexpected character in array @@ -781,6 +786,11 @@ StructUtils.dictlike(::CustomJSONStyle, ::Type{DictlikeViaCustomStyle}) = true let res = JSON.parse("""{"a": 1, "b": 2}""", DictlikeViaCustomStyle; style=CustomJSONStyle()) @test res.vals == Dict("a" => 1, "b" => 2) end + # https://github.com/JuliaIO/JSON.jl/issues/462 - structlike dispatch on custom JSONStyle must reach user method + let json = JSON.json(Ref{Int}(1); style=RefValueStyle()) + @test json == "1" + @test JSON.parse(json, Base.RefValue{Int}; style=RefValueStyle())[] == 1 + end @test isequal(JSON.parse("{\"num\": 1,\"den\":null}", @NamedTuple{num::Int, den::Union{Int, Missing}}; null=missing, style=StructUtils.DefaultStyle()), (num=1, den=missing)) # choosetype field tag on Any struct field @test JSON.parse("{\"id\":1,\"any\":{\"type\":\"int\",\"value\":10}}", Q) == Q(1, (type="int", value=10))