diff --git a/src/parse.jl b/src/parse.jl index b4c5a9c..3468ea9 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -389,8 +389,30 @@ 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) + +struct _NoCustomLazyLift end +const _NO_CUSTOM_LAZY_LIFT = _NoCustomLazyLift() + +StructUtils.lift(::JSONStyle, ::Type{T}, ::LazyValues, tags) where {T} = _NO_CUSTOM_LAZY_LIFT +StructUtils.lift(::JSONStyle, ::Type{T}, ::LazyValues) where {T} = _NO_CUSTOM_LAZY_LIFT + +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 JSONStyle || return nothing + ret = StructUtils.lift(inner, T, x, tags) + ret === _NO_CUSTOM_LAZY_LIFT || return _liftresult(ret, skip(x)) + ret = StructUtils.lift(inner, T, x) + ret === _NO_CUSTOM_LAZY_LIFT || return _liftresult(ret, skip(x)) + return nothing +end function StructUtils.lift(style::JSONReadStyle, ::Type{T}, x::LazyValues) where {T<:AbstractArray{E,0}} where {E} m = T(undef) @@ -401,6 +423,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) @@ -444,10 +470,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 500fed6..a20980a 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -2,6 +2,9 @@ using JSON, StructUtils, UUIDs, Dates, Test struct CustomJSONStyle <: JSON.JSONStyle end struct RefValueStyle <: JSON.JSONStyle end +struct DateStringStyle <: JSON.JSONStyle end +struct DateObjectStyle <: JSON.JSONStyle end +struct DateMaterializedObjectStyle <: JSON.JSONStyle end struct A a::Int @@ -241,6 +244,13 @@ 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 +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[]) +JSON.lower(::DateMaterializedObjectStyle, d::Date) = (; time=string(d)) +JSON.lift(::DateMaterializedObjectStyle, ::Type{Date}, x::JSON.Object) = Date(x["time"]) + @testset "JSON.parse" begin @testset "errors" begin # Unexpected character in array @@ -782,6 +792,10 @@ StructUtils.lift(::RefValueStyle, ::Type{Base.RefValue{Int}}, x::Integer) = Ref{ 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) + @test JSON.parse(JSON.json(Date(2023, 1, 1); style=DateMaterializedObjectStyle()), Date; style=DateMaterializedObjectStyle()) == 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)