quinnj / JSON3.jl

Other
214 stars 47 forks source link

Errors deserialising structs without fields #270

Open freemin7 opened 1 year ago

freemin7 commented 1 year ago

I have a wrapper type which might contains empty object which might be omitted.

using StructTypes 

abstract type Foo end

struct TypedWrapper{T}
    wrap::T
    type::Symbol
end

struct Intainter <: Foo a::Int end
struct NoBrainer <: Foo end

StructTypes.StructType(::Type{TypedWrapper}) = StructTypes.AbstractType()
StructTypes.subtypekey(::Type{TypedWrapper}) = :type
StructTypes.subtypes(::Type{TypedWrapper}) = (brain = TypedWrapper{NoBrainer},  int = TypedWrapper{Intainter})

@inline StructTypes.isempty(::Type{TypedWrapper}, ::NoBrainer) = true
StructTypes.omitempties(::Type{TypedWrapper}) = (:wrap)

StructTypes.StructType(::Type{TypedWrapper{T}}) where T <: Foo = StructTypes.Struct()

using JSON3

TestBuffer = PipeBuffer()

for m in [ TypedWrapper(Intainter(8),:int) TypedWrapper(NoBrainer(),:brain) ]
    println(TestBuffer, JSON3.write(m))
    println(JSON3.write(m))
end

# Problems begin here

for i in 1:2
  println(JSON3.read(TestBuffer, TypedWrapper))  ## Just prints 1 line when it should print 2 lines
end
JSON3.write(TypedWrapper(NoBrainer(),:brain)) ## Shouldn't serialize the struct as NoBrainer()

JSON3.read("{\"type\":\"brain\"}"
, TypedWrapper) ## shouldn't error
JSON3.read("{\"wrap\":{},\"type\":\"brain\"}"
, TypedWrapper) ## shouldn't error
quinnj commented 1 year ago

Ah, part of the issue here is that StructTypes now has a new struct type SingletonType that is applied automatically to NoBrainer, thus resulting in the serialization like "{\"wrap\":\"NoBrainer()\",\"type\":\"brain\"}". If you define StructTypes.StructType(::Type{NoBrainer}) = StructTypes.Struct(), then your example works for me. The other error can be solved by defining an appropriate constructor that takes nothing, like TypedWrapper{NoBrainer}(::Nothing, type::Symbol) = TypedWrapper{NoBrainer}(NoBrainer(), type) (we attempted to document this but I know it's a bit buried. The key idea is that if you have structs that can be constructed when fields are missing in the JSON, then you should handle that in a custom constructor).

freemin7 commented 1 year ago

What's the motivation behind the singleton type?

quinnj commented 1 year ago

It helps in other (non-JSON) serialization use-cases and for JSON, provides a more descriptive serialization form (instead of just the default empty object output {}).

freemin7 commented 1 year ago

I am not sure which one is more intuitive and should be the default behavior. Do you think better documentation about this should go into JSON3 or StructTypes?