Disclaimer: not a JSON3 contributor/maintainer here.
The issue raised by you has less to do with JSON3
or StructTypes
packages - it touches the more general discussion of serialization/deserialization, with a particular focus on mutable
data types.
Technically speaking, you can always do this:
using JSON3
using StructTypes
@kwdef mutable struct MyType2
c::Float64 = 3.0
MyType2(c) = c < 0.0 ? error("bar") : new(c)
end
StructTypes.StructType(::Type{MyType}) = StructTypes.Mutable()
Base.@kwdef mutable struct MyType
a::Int = 1
b::MyType2 = MyType2(3.0)
MyType(a, b) = a > 10 ? error("foo") : new(a, b)
end
StructTypes.StructType(::Type{MyType2}) = StructTypes.Mutable()
v = MyType(9, MyType2(3.0))
v.a = 11
v.b.c = -3.0
serialized = JSON3.write(v)
deserialized = JSON3.read(serialized, MyType; parsequoted=true)
@info v
@info deserialized
Now, v
exists in your session in a state not achievable by the direct usage of the constructor. And that is totally legitimate for a mutable struct
: you can have restrictions in the constructor but mutate your struct
fields at a later time (imagine the scenario where you initiate a “person” at age 0 - but increase the mutable field age
as the person grows older).
You might what to serialize the object v
and deserialize it later.
The deserialization has nothing to do with constructing a new object (via new()
) - the deserialization ensures that a previous state of your object will be restored: that is part of the job of StructTypes
- and subsequently of the JSON3
package.
Imposing the restrictions from new()
would directly violate the very meaning of restoring an object to a previous state
. And would break the package deserialization functionality.
So, when you say,
you basically say that you expect JSON3 to give up the deserialization
for mutable structs and do something else.
A solution for your issue might consist in building your own validator and checking the deserialized objects (and even instantiating the objects to the desired values/state if the check fails).
Also, if you don’t want some states to be representable for your struct
type, you can do something like this:
@kwdef mutable struct MyType2
c::Float64 = 3.0
MyType2(c) = c < 0.0 ? error("bar") : new(c)
end
function Base.setproperty!(v::MyType2, field::Symbol, value)
field == :c && value < 0.0 && error("bar")
setfield!(v, field, value)
end
mt2 = MyType2(3.0)
# this will fail now
mt2.c = -3.0
Imposing such constraint will at least prevent “invalid” states from being reached in your program - and you’ll never actually end up doing serialization of the “illegal state.”
P. S. In the case of the immutable structs, the premise is that the field values will not change once the struct is instantiated - consequently, you will not be able to serialize some “illegal state” of your struct (thus, there is no question of what is going to happen at the deserialization time).
Now, there can be both custom serialization and deserialization behaviors implemented. In such scenarios can be totally legitimate to impose any restrictions you want (both related to the new()
constraints or something else - even arbitrary).