Extended deserialization

Serde.jl allows users to define how their custom data will be processed during deserialization.

Serde.deserMethod
Serde.deser(::Type{T}, data) -> T

Main function of this module which can construct an object of type T from another object data. Can deserialize complex object with a deep nesting.

Function deser supports:

  • Deserialization from Dict and Vector to Struct
  • Deserialization from Dict and Vector to Vector of Struct
  • Deserialization from Dict to Dict
  • Typecasting during deserialization
  • Default value for struct arguments (see Serde.default_value)
  • Custom name for struct arguments (see Serde.custom_name)
  • Empty type definition (see Serde.isempty)
  • Deserializing missing and nothing (see Serde.nulltype)

Examples:

julia> struct Info
           id::Int64
           salary::Int64
       end

julia> struct Person
           name::String
           age::Int64
           info::Info
       end

julia> info_data = Dict("id" => 12, "salary" => 2500);

julia> person_data = Dict("name" => "Michael", "age" => 25, "info" => info_data);

julia> Serde.deser(Person, person_data)
Person("Michael", 25, Info(12, 2500))
source

Custom deserialization behavior

If you need to deserialize non-standard custom data types, it will be useful to define a behavior to handle them.

Serde.deserMethod
Serde.deser(::Type{T}, ::Type{E}, data::D) -> E

Internal function that is used to deserialize data to fields with type E of custom type T. Supports user overriding for custom types.

Note

This function is not used explicitly and can only be overridden for the deserialization process.

Examples:

Let's make a custom type Order with fields price and date.

using Dates

struct Order
    price::Int64
    date::DateTime
end

Now, we define a new method Serde.deser for the custom type Order. This method will be called for each field of Order that is of type DateTime and has been passed a String value.

function Serde.deser(
    ::Type{T},
    ::Type{E},
    x::String
)::E where {T<:Order,E<:DateTime}
    return DateTime(x)
end

After that, if we try to deserialize a dictionary that has a key date with a String value, it will correctly convert the String to a DateTime value.

julia> Serde.deser(Order, Dict("price" => 1000, "date" => "2024-01-01T10:20:30"))
Order(1000, DateTime("2024-01-01T10:20:30"))
source

Empty values handling

We can also determine which data types and their values will be treated as nothing.

Base.isemptyFunction
Serde.isempty(::Type{T}, x) -> false

This function determines the condition under which the passed value x for some custom type T can be treated as nothing. Supports user overriding for custom types. Initially, all values are set to false.

Note

This function is not used explicitly and can only be overridden for the deserialization process.

See also Serde.nulltype, Serde.default_value.

Examples:

Let's make a custom type Computer with the following fields. The gpu field may be either a String or Nothing.

struct Computer
    cpu::String
    ram::Int64
    gpu::Union{Nothing,String}
end

Now, we define a new method Serde.isempty for the custom type Computer. This method will be called for each field of Computer that has been passed a String value.

function Serde.isempty(::Type{Computer}, x::String)
    return x == ""
end

So, if we try to deserialize a dictionary with a key gpu containing an empty string, it will set a nothing value for such a field in Computer.

julia> Serde.deser(Computer, Dict("cpu" => "i7-12900", "ram" => 32, "gpu" => "rtx-4090"))
Computer("i7-12900", 32, "rtx-4090")

julia> Serde.deser(Computer, Dict("cpu" => "i3-12100", "ram" => 16, "gpu" => ""))
Computer("i3-12100", 16, nothing)
source

Names aliases

Sometimes, the field names of the incoming data structure differ from their intended destination. In this case, it is convenient to specify name aliases.

Serde.custom_nameFunction
Serde.custom_name(::Type{T}, ::Val{x}) -> x

This function is used to define an alias name for field x of type T. Supports user overriding for custom types. Initially, all passed names must be equivalent to the target names. Methods of this function must return a Symbol or a String value.

Note

This function is not used explicitly and can only be overridden for the deserialization process.

Examples:

Let's make a custom type Phone with one field price.

struct Phone
    price::Int64
end

Now, we can define a new method Serde.custom_name for the type Phone and its field price.

function Serde.custom_name(::Type{Phone}, ::Val{:price})
    return "cost"
end

After that, if we try to deserialize a dictionary with an alias key "cost", it will match with the field price of type Phone.

julia> Serde.deser(Phone, Dict("cost" => 1000))
Phone(1000)
source

Custom default values

We can also define default values for certain data types.

Serde.default_valueFunction
Serde.default_value(::Type{T}, ::Val{x}) -> nothing

This function is used to define default values for field x of type T. Supports user overriding for custom types. Initially, all values are set to nothing.

Note

This function is not used explicitly and can only be overridden for the deserialization process.

See also Serde.isempty, Serde.nulltype.

Examples:

Let's make a custom type TimeZone with the field gmt.

struct TimeZone
    gmt::String
end

Now, we can define a new method Serde.default_value for the type TimeZone and its field gmt.

function Serde.default_value(::Type{TimeZone}, ::Val{:gmt})
    return "UTC+3"
end

After that, if we try to deserialize a dictionary without a key gmt, it will be filled with the default value "UTC+3".

julia> Serde.deser(TimeZone, Dict{String,Any}())
TimeZone("UTC+3")
source

Null types handling

Finally, we can determine the 'nulltype' for custom types when they are empty or not specified at all.

Serde.nulltypeFunction
Serde.nulltype(::Type{T}) -> nothing

Defines behavior when the value for a field of type T is empty (according to Serde.isempty) or not specified. Supports user overriding for custom types. Initially, for all types, it is set to nothing (in case of type Missing, it returns the missing value).

Note

This function is not used explicitly and can only be overridden for the deserialization process.

See also Serde.isempty, Serde.default_value.

Examples

Let's make a custom type Computer with the following fields.

struct Computer
    cpu::String
    gpu::String
end

For clarity, we also define the Serde.isempty method.

Serde.isempty(::Type{Computer}, x::String) = x == ""

Next, we define a new method Serde.nulltype for the custom type Computer. This method will be called for each type String that has been passed to a Serde.deser method.

Serde.nulltype(::Type{String}) = "N/A"

And, if we try to deserialize a dictionary with values of type String containing an empty string or not specified at all, it will set a "N/A" value for such fields in Computer.

julia> Serde.deser(Computer, Dict("cpu" => "i7-12900", "gpu" => ""))
Computer("i7-12900", "N/A")

julia> Serde.deser(Computer, Dict{String,Any}())
Computer("N/A", "N/A")
source