From 99f8b7804a6df6eb737f21af948cf6da127dbcdd Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Thu, 14 May 2026 14:14:47 -0600 Subject: [PATCH] fix(parse): honor custom style lifts Fixes #434 --- src/parse.jl | 32 ++++++++++++++++++++++++++++---- test/parse.jl | 10 ++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/parse.jl b/src/parse.jl index 1a4745a..fa45171 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -381,8 +381,28 @@ StructUtils.lift(st::JSONReadStyle, ::Type{T}, x::PtrString) where {T} = StructUtils.liftkey(::JSONReadStyle, ::Type{T}, x::AbstractString) where {T<:Integer} = Base.parse(T, x) StructUtils.liftkey(::JSONReadStyle, ::Type{T}, x::AbstractString) where {T<:AbstractFloat} = Base.parse(T, x) -StructUtils.lift(style::JSONReadStyle, ::Type{T}, x, tags) where {T} = StructUtils.lift(style.style, T, x, tags) -StructUtils.lift(style::JSONReadStyle, ::Type{T}, x) where {T} = StructUtils.lift(style.style, T, x) +_isliftpair(x) = x isa Tuple && !(x isa NamedTuple) && length(x) == 2 +_liftresult(x, st) = _isliftpair(x) ? x : (x, StructUtils.defaultstate(st)) +_liftresult(x, pos::Int) = _isliftpair(x) ? x : (x, pos) + +StructUtils.lift(style::JSONReadStyle, ::Type{T}, x, tags) where {T} = + _liftresult(StructUtils.lift(style.style, T, x, tags), style) +StructUtils.lift(style::JSONReadStyle, ::Type{T}, x) where {T} = + _liftresult(StructUtils.lift(style.style, T, x), style) + +function customlazylift(style::JSONReadStyle, ::Type{T}, x::LazyValues, tags) where {T} + inner = style.style + inner isa StructUtils.DefaultStyle && return nothing + m4 = which(StructUtils.lift, Tuple{typeof(inner), Type{T}, typeof(x), typeof(tags)}) + if m4.module !== StructUtils + return _liftresult(StructUtils.lift(inner, T, x, tags), skip(x)) + end + m3 = which(StructUtils.lift, Tuple{typeof(inner), Type{T}, typeof(x)}) + if m3.module !== StructUtils + return _liftresult(StructUtils.lift(inner, T, x), skip(x)) + end + return nothing +end function StructUtils.lift(style::JSONReadStyle, ::Type{T}, x::LazyValues) where {T<:AbstractArray{E,0}} where {E} m = T(undef) @@ -393,6 +413,10 @@ end function StructUtils.lift(style::JSONReadStyle, ::Type{T}, x::LazyValues, tags=(;)) where {T} type = gettype(x) buf = getbuf(x) + if type == JSONTypes.OBJECT || type == JSONTypes.ARRAY + custom = customlazylift(style, T, x, tags) + custom === nothing || return custom + end if type == JSONTypes.STRING GC.@preserve buf begin ptrstr, pos = parsestring(x) @@ -436,10 +460,10 @@ function StructUtils.lift(style::JSONReadStyle, ::Type{T}, x::LazyValues, tags=( val1 = out.value # big switch here for --trim verify-ability if val1 isa Object{String,Any} - val, _ = StructUtils.lift(style, T, val1) + val, _ = StructUtils.lift(style, T, val1, tags) return val, pos elseif val1 isa Vector{Any} - val, _ = StructUtils.lift(style, T, val1) + val, _ = StructUtils.lift(style, T, val1, tags) return val, pos elseif val1 isa String val, _ = StructUtils.lift(style, T, val1) diff --git a/test/parse.jl b/test/parse.jl index 172fb0e..71779c5 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -1,6 +1,8 @@ using JSON, StructUtils, UUIDs, Dates, Test struct CustomJSONStyle <: JSON.JSONStyle end +struct DateStringStyle <: JSON.JSONStyle end +struct DateObjectStyle <: JSON.JSONStyle end struct A a::Int @@ -236,6 +238,11 @@ Base.valtype(::DictlikeViaCustomStyle) = Int StructUtils.addkeyval!(a::DictlikeViaCustomStyle, k, v) = StructUtils.addkeyval!(a.vals, k, v) StructUtils.dictlike(::CustomJSONStyle, ::Type{DictlikeViaCustomStyle}) = true +JSON.lower(::DateStringStyle, d::Date) = string(d) +JSON.lift(::DateStringStyle, ::Type{Date}, x::String) = Date(x) +JSON.lower(::DateObjectStyle, d::Date) = (; time=string(d)) +JSON.lift(::DateObjectStyle, ::Type{Date}, x::JSON.LazyValue) = Date(x.time[]) + @testset "JSON.parse" begin @testset "errors" begin # Unexpected character in array @@ -774,6 +781,9 @@ StructUtils.dictlike(::CustomJSONStyle, ::Type{DictlikeViaCustomStyle}) = true JSON.lift(::CustomJSONStyle, ::Type{Rational}, x) = Rational(x.num[], x.den[]) @test JSON.parse("{\"num\": 1,\"den\":3}", Rational; style=CustomJSONStyle()) == 1//3 @test JSON.parse("{\"num\": 1,\"den\":3}", Rational; style=CustomJSONStyle(), unknown_fields=:error) == 1//3 + # https://github.com/JuliaIO/JSON.jl/issues/434 + @test JSON.parse(JSON.json(Date(2023, 1, 1); style=DateStringStyle()), Date; style=DateStringStyle()) == Date(2023, 1, 1) + @test JSON.parse(JSON.json(Date(2023, 1, 1); style=DateObjectStyle()), Date; style=DateObjectStyle()) == Date(2023, 1, 1) # https://github.com/JuliaIO/JSON.jl/issues/453 - dictlike dispatch on custom JSONStyle must reach user method let res = JSON.parse("""{"a": 1, "b": 2}""", DictlikeViaCustomStyle; style=CustomJSONStyle()) @test res.vals == Dict("a" => 1, "b" => 2)