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.deserFunction
Serde.deser(::Type{T}, data) -> T
Serde.deser(strategy, ::Type{T}, data) -> T

Construct 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:

  1. Look up the key via Serde.deser_name.
  2. If absent, fall back to Serde.deser_default or Serde.nulltype.
  3. Apply Serde.isempty_value / Serde.deser_transform.
  4. Convert to the field type.
  5. 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 a Dict{String,Any}, Vector{Any}, or a scalar.
  • strategy: optional context object (e.g. CamelCase(), PascalCase()).

Returns

A fully constructed value of type T.

Throws

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.

source
Serde.deser(strategy, ::Type{T}, ::Type{FieldType}, data) -> FieldType

Field-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)
end

See also: Serde.deser, Serde.deser_transform.

source

Renaming input keys

Serde.deser_nameFunction
Serde.deser_name(::Type{T}, ::Val{field}) -> Symbol
Serde.deser_name(strategy, ::Type{T}, ::Val{field}) -> Symbol

Returns 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 a Val.

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}) = :createdAt

See also: Serde.ser_name, CamelCase, PascalCase.

source

Default values

Serde.has_defaultFunction
Serde.has_default(::Type{T}, ::Val{field}) -> Bool
Serde.has_default(strategy, ::Type{T}, ::Val{field}) -> Bool

Returns 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}) = 30

See also: Serde.deser_default, MissingFieldError.

source
Serde.deser_defaultFunction
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}) = 8080

See also: Serde.has_default.

source

Null handling

Serde.nulltypeFunction
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)nothing
  • nulltype(Missing)missing
  • nulltype(Union{Nothing,T})nothing
  • nulltype(Union{Missing,T})missing

For all other types the default returns nothing, but this can be overridden.

See also: Serde.isempty_value.

source
Serde.isempty_valueFunction
Serde.isempty_value(::Type{T}, ::Val{field}, value) -> Bool
Serde.isempty_value(strategy, ::Type{T}, ::Val{field}, value) -> Bool

Returns 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.0

See also: Serde.nulltype.

source

Value transformation

Serde.deser_transformFunction
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.0

See also: Serde.deser_validate.

source

Validation

Serde.deser_validateFunction
Serde.deser_validate(::Type{T}, ::Val{field}, value) -> nothing
Serde.deser_validate(strategy, ::Type{T}, ::Val{field}, value) -> nothing

Validates 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"))
end

See also: ValidationError, Serde.deser_transform.

source