Extended deserialization
Serde.jl lets you customise how data is mapped onto your types by overriding a small set of trait functions.
Core deserialization hook
Serde.deser — Function
Serde.deser(::Type{T}, data) -> T
Serde.deser(strategy, ::Type{T}, data) -> TConstruct an object of type T from already-parsed data (a Dict, Vector, or scalar).
This is the core deserialization primitive. Format-specific functions (from_json, from_yaml, etc.) first parse raw bytes into Julia data structures, then delegate to deser to map those structures onto the target type.
The deserialization pipeline for each field of a struct:
- Look up the key via
Serde.deser_name. - If absent, fall back to
Serde.deser_defaultorSerde.nulltype. - Apply
Serde.isempty_value/Serde.deser_transform. - Convert to the field type.
- Run
Serde.deser_validate.
Pass a context object strategy as the first argument to apply context-aware trait overrides (such as field renaming via CamelCase).
Arguments
::Type{T}: target type to construct.data: parsed input — typically aDict{String,Any},Vector{Any}, or a scalar.strategy: optional context object (e.g.CamelCase(),PascalCase()).
Returns
A fully constructed value of type T.
Throws
MissingFieldError: a required field is absent and has no default.TypeMismatchError: a field value cannot be converted to the declared type.ValidationError: a field fails a customdeser_validatecheck.
Examples
julia> struct Point; x::Int; y::Int; end
julia> Serde.deser(Point, Dict("x" => 1, "y" => 2))
Point(1, 2)
julia> Serde.deser(Point, Dict("x" => "3", "y" => "4")) # string → int coercion
Point(3, 4)
julia> Serde.deser(CamelCase(), Point, Dict("x" => 1, "y" => 2))
Point(1, 2)See also: Serde.to_deser, from_json, Serde.deser_name.
Serde.deser(strategy, ::Type{T}, ::Type{FieldType}, data) -> FieldTypeField-level deserialization hook. Called by the engine when converting data to the type declared for a particular field.
Override this method to implement custom type conversions that cannot be handled by Serde.deser_transform. The first type parameter T is the enclosing struct; the second is the field's declared type.
Examples
using Dates
struct LogEntry
timestamp::DateTime
message::String
end
# Accept Unix timestamps (integers) as DateTime
function Serde.deser(strategy, ::Type{LogEntry}, ::Type{DateTime}, x::Integer)
return Dates.unix2datetime(x / 1000)
endSee also: Serde.deser, Serde.deser_transform.
Renaming input keys
Serde.deser_name — Function
Serde.deser_name(::Type{T}, ::Val{field}) -> Symbol
Serde.deser_name(strategy, ::Type{T}, ::Val{field}) -> SymbolReturns the key name to look up in the input data when deserializing field of type T.
Override this method to create field name aliases — for example, to accept camelCase JSON keys into snake_case Julia struct fields. The default implementation returns field unchanged.
The context-aware variant is called when a context object is passed to from_json / from_yaml / etc. Case-renaming contexts such as CamelCase override this method.
Arguments
::Type{T}: the struct type being deserialized.::Val{field}: the Julia field name as aVal.
Returns
The Symbol (or String) key to look up in the parsed data.
Examples
struct User
user_name::String
created_at::String
end
# Accept "userName" in JSON but store as user_name
Serde.deser_name(::Type{User}, ::Val{:user_name}) = :userName
Serde.deser_name(::Type{User}, ::Val{:created_at}) = :createdAtSee also: Serde.ser_name, CamelCase, PascalCase.
Default values
Serde.has_default — Function
Serde.has_default(::Type{T}, ::Val{field}) -> Bool
Serde.has_default(strategy, ::Type{T}, ::Val{field}) -> BoolReturns true if field of type T has a registered default value.
This predicate separates "the default is nothing" from "no default exists at all". When has_default returns false and the field is absent from the input, deserialization throws MissingFieldError (unless the field type is nullable).
Override both has_default and Serde.deser_default together:
struct Config
host::String
port::Int
timeout::Int
end
Serde.has_default(::Type{Config}, ::Val{:timeout}) = true
Serde.deser_default(::Type{Config}, ::Val{:timeout}) = 30See also: Serde.deser_default, MissingFieldError.
Serde.deser_default — Function
Serde.deser_default(::Type{T}, ::Val{field})
Serde.deser_default(strategy, ::Type{T}, ::Val{field})Returns the default value for field of type T when the field is absent from the input.
Only called when Serde.has_default(T, Val(field)) returns true. The return value must be assignable to the field's declared type.
Examples
struct Server
host::String
port::Int
end
Serde.has_default(::Type{Server}, ::Val{:port}) = true
Serde.deser_default(::Type{Server}, ::Val{:port}) = 8080See also: Serde.has_default.
Null handling
Serde.nulltype — Function
Serde.nulltype(::Type{T})Returns the sentinel value to use when a field of type T is null or empty in the input.
The built-in specializations are:
nulltype(Nothing)→nothingnulltype(Missing)→missingnulltype(Union{Nothing,T})→nothingnulltype(Union{Missing,T})→missing
For all other types the default returns nothing, but this can be overridden.
See also: Serde.isempty_value.
Serde.isempty_value — Function
Serde.isempty_value(::Type{T}, ::Val{field}, value) -> Bool
Serde.isempty_value(strategy, ::Type{T}, ::Val{field}, value) -> BoolReturns true if value should be treated as logically empty and replaced with Serde.nulltype(FieldType) during deserialization.
The default implementation always returns false. Override to treat domain-specific sentinel values (e.g. empty strings, zero, "N/A") as null:
Examples
struct Trade
price::Union{Nothing,Float64}
volume::Union{Nothing,Float64}
end
# Treat 0.0 as missing price
Serde.isempty_value(::Type{Trade}, ::Val{:price}, v::Float64) = v == 0.0See also: Serde.nulltype.
Value transformation
Serde.deser_transform — Function
Serde.deser_transform(::Type{T}, ::Type{FieldType}, value)
Serde.deser_transform(strategy, ::Type{T}, ::Type{FieldType}, value)Transforms value before it is stored into a field of FieldType during deserialization of type T. Called after null-checking but before type conversion.
Use this hook for lightweight value normalization (e.g. trimming strings, unit conversion) that applies uniformly across all sources.
Examples
struct Measurement
value_cm::Float64
end
# Input is in millimetres; convert to centimetres
Serde.deser_transform(::Type{Measurement}, ::Type{Float64}, v::Number) = v / 10.0See also: Serde.deser_validate.
Validation
Serde.deser_validate — Function
Serde.deser_validate(::Type{T}, ::Val{field}, value) -> nothing
Serde.deser_validate(strategy, ::Type{T}, ::Val{field}, value) -> nothingValidates value for field of type T after deserialization and type conversion. The method should return nothing on success and throw a ValidationError (or any other exception) on failure.
Examples
struct Order
quantity::Int
price::Float64
end
function Serde.deser_validate(::Type{Order}, ::Val{:quantity}, v::Int)
v > 0 || throw(ValidationError(Order, :quantity, v, "quantity must be positive"))
end
function Serde.deser_validate(::Type{Order}, ::Val{:price}, v::Float64)
v > 0 || throw(ValidationError(Order, :price, v, "price must be positive"))
endSee also: ValidationError, Serde.deser_transform.