Migration guides
This guide provides an overview of how to migrate your code from either the pre-1.0 JSON.jl package to the 1.0 release or from JSON3.jl. The 1.0 release introduces several improvements and changes, particularly in how JSON is read and written, leveraging StructUtils.jl for customization and extensibility. Below, we outline the key differences and provide step-by-step instructions for updating your code.
Migration guide from pre-1.0 -> 1.0
Writing JSON
JSON.json
- What stayed the same:
- Produces a compact String by default
- Can automatically serialize basic structs in a sensible way
- Can take an integer 2nd argument to induce "pretty printing" of the JSON output
- What changed:
- Can now pass
JSON.json(x; pretty=true)
orJSON.json(x; pretty=4)
to control pretty printing - Can pass filename as first argument to write JSON directly to a file
JSON.json(file, x)
the file name is returned - Can pass any
IO
as 1st argument to write JSON to it:JSON.json(io, x)
- Circular reference tracking is fixed/improved (previously peer references were written as null)
- Explicit keyword arguments to control a number of serialization features, including:
omit_null::Bool
whethernothing
/missing
Julia values should be skipped when serializingomit_empty::Bool
whether empty Julia collection values should be skipped when serializingallownan::Bool
similar to the parsing keyword argument to allow/disallow writing of invalid JSON valuesNaN
,-Inf
, andInf
ninf::String
the string to write ifallownan=true
and serializing-Inf
inf::String
the string to write ifallownan=true
and serializingInf
nan::String
the string to write ifallownan=true
and serializingNaN
jsonlines::String
when serializing an array, write each element independently on a new line as an implicit array; can be read back when parsing by also passingjsonlines=true
inline_limit::Int
threshold number of elements in an array under which an array should be printed on a single line (only applicable when pretty printing)float_style::Symbol
allowed values are:shortest
,:fixed
, and:exp
corresponding to printf format styles,%g
,%f
, and%e
, respectivelyfloat_precision::Int
number of decimal places to use when printing floats
- Can now pass
- Why the changes:
- Mostly just modernizing the interfaces (use of keyword arguments vs. positional)
- Utilizing multiple dispatch to combine
JSON.print
andJSON.json
and provide convenience for writing to files - Most opened issues over the last few years were about providing more controls around writing JSON without having to completely implement a custom serializer
- More consistency with
JSON.parse
keyword args withallownan
andjsonlines
- What stayed the same:
JSON.print
- What stayed the same:
- Technically still defined for backwards compatibility, but just calls
JSON.json
under the hood
- Technically still defined for backwards compatibility, but just calls
- Why the changes:
- Not necessary as all the functionality can be combined without ambiguity or overlap with
JSON.json
- Not necessary as all the functionality can be combined without ambiguity or overlap with
- What stayed the same:
JSON.lower
- What stayed the same:
- Still used to transform Julia values into JSON-appropriate values
- What changed:
lower
technically now lives in the StructUtils.jl package (though overloading in JSON is fine)- Can overload for a specific "style" for non-owned types, like
struct MyStyle <: JSON.JSONStyle end
, thenJSON.lower(::MyStyle, x::Rational) = (den=x.den, num=x.num)
, then have the style used when writing likeJSON.json(1//3; style=MyStyle())
- Probably don't need to
lower
except in rare cases; there are defaultlower
defintions for common types and most structs/AbstractDict/AbstractArray will work out of the box;lower
is mostly useful when wanting to have the JSON output of a struct be a string or a number, for example, so going between aggregate/non-aggregate from Julia to JSON
- Why the changes:
- Along with the new corresponding
lift
interface, thelower
+lift
combination is a powerful generalization of doing "domain transformations"
- Along with the new corresponding
- What stayed the same:
JSON.StructuralContext
/JSON.show_json
/JSON.Serialization
- What stayed the same:
- These have been removed in favor of simpler interfaces and custom
JSONStyle
subtypes + overloads
- These have been removed in favor of simpler interfaces and custom
- Why the changes:
- The use of distinct contexts for different writing styles (pretty, compact) is unnecessary and led to code duplication
- There was often confusion about whether a custom Serialization or StructuralContext was needed and what intefaces were then required to implement
- The need to customize separators, delimiters, and indentation, while powerful, can be accomplished much simpler via keyword arguments or is not necessary at all (i.e. JSON.jl shouldn't be too concerned with how to produce anything that isn't JSON)
- Instead of overloading showstring/showelement/showkey/showpair/showjson,
lower
can be used to accomplish any requirements of "overloading" how values are serialized; the addition of "styles" also allows customizing for non-owned types instead of needing a custom context + `showjson` method
- What stayed the same:
JSONText
- What changed:
- Nothing;
JSONText
can still be used to have a JSON-formatted string be written as-is when serializing
- Nothing;
- What changed:
Reading JSON
JSON.parse
/JSON.parsefile
- What stayed the same:
- These functions take the same JSON input arguments (String, IO, or filename for
parsefile
) - The
dicttype
,allownan
, andnull
keyword arguments all remain and implement the same functionality
- These functions take the same JSON input arguments (String, IO, or filename for
- What changed:
JSON.Object{String, Any}
is now the default type used when parsing instead ofDict{String, Any}
;JSON.Object
is a drop-in replacement forDict
, supporting theAbstractDict
interface, mutation, dot-access (getproperty) to keys, memory and performance benefits for small objects vs.Dict
, and preserves the JSON order of keys. For large objects (hundreds or thousands of keys), or to otherwise restore the pre-1.0 behavior, you can doJSON.parse(json; dicttype=Dict{String, Any})
.- The
inttype
keyword argument has been removed - The
allownan
keyword argument now defaults tofalse
instead oftrue
to provide a more accurate JSON specification behavior as the default - The
use_mmap
keyword argument has been removed fromparsefile
; mmapping will now be decided automatically by the package and any mmaps used for parsing will be completely finalized when parsing has finished - Numbers in JSON will now be parsed as
Int64
,BigInt
,Float64
, orBigFloat
, instead of onlyInt64
orFloat64
. Many JSON libraries support arbitrary precision ints/floats, and now JSON.jl does too. JSON.parse(json, T)
andJSON.parse!(json, x)
variants have been added for constructing a Julia value from JSON, or mutating an existing Julia value from JSON;JSON.parsefile(json, T)
andJSON.parsefile!(json, x)
are also supported; see JSON Reading for more details
- Why the changes:
- The
inttype
keyword argument is rare among other JSON libraries and doesn't serve a strong purpose; memory gains from possibly using smaller ints is minimal and leads to more error-prone code via overflows by trying to force integers into non-standard small types - For the
allownan
default value change, there are many benchmarks/JSON-accuracy checking test suites that enforce adherance to the specification; following the specification by default is recommended and common across language JSON libraries - Mmapping is an internal detail that most users shouldn't worry about anyway, and it can be done transparently without any outside affect to the user
- The
- What stayed the same:
JSONText
JSONText
can now also be used while parsing, as a field type of a struct or directly to return the raw JSON (similar to how writing withJSONText
works)
Migration guide for JSON3.jl
The JSON.jl 1.0 release incorporates many of the design ideas that were originally developed in JSON3.jl. This guide helps you transition your code from JSON3.jl to JSON.jl 1.0, highlighting what's changed, what's similar, and the best way to update your code.
Writing JSON
JSON3.write
→JSON.json
- What stayed the same:
- The core functionality of serializing Julia values to JSON remains the same
- Support for serializing custom structs in a sensible way
- Both can output to a string or IO
- What changed:
- Function name:
JSON3.write
becomesJSON.json
- Direct file writing: Instead of
open(file, "w") do io; JSON3.write(io, x); end
, you can useJSON.json(file, x)
- Customization framework: JSON3.jl uses StructTypes.jl, while JSON.jl 1.0 uses StructUtils.jl
- Pretty printing:
JSON3.pretty(JSON3.write(x))
becomesJSON.json(x; pretty=true)
- Special numeric values: In JSON3.jl, writing NaN/Inf/-Inf required passing
allow_inf=true
, in JSON.jl 1.0 you passallownan=true
- Function name:
- Why the changes:
- Preference was given to existing JSON.jl names where possible (
JSON.json
,allownan
, etc) - JSON3 pretty printing support was an example of "bolted on" functionality that had a number of issues because it tried to operate on its own; in
JSON.json
, pretty printing is directly integrated with the core serializing code and thus doesn't suffer the same ergonomic problems - StructUtils.jl is overall simpler and provides much more functionality "by default" meaning its much more invisible for majority of use-cases. Its design is the direct result of wanting to provide roughly similar functionality as StructTypes.jl but avoiding the pitfalls and architectural complexities it had
- Preference was given to existing JSON.jl names where possible (
- What stayed the same:
Custom Type Serialization
- What stayed the same:
- Both provide a way to customize how types are serialized
- Both support serializing custom types to any valid JSON value
- What changed:
- Interface: No need to declare
StructTypes.StructType
explicitly on structs (StructUtils.jl can detect the vast majority of struct types automatically) - Non-owned types: JSON.jl (via StructUtils) provides the concept of defining a custom
StructStyle
subtype that allows customizing the lowering/lifting overloads of non-owned types (JSON3.jl had repeated requests/issues with users wanting more control over non-owned types without pirating)
- Interface: No need to declare
- Why the changes:
- As noted above, the overall design of StructUtils is simpler and more automatic, with the default definitions working in the vast majority of cases. If you're the author of a custom Number, AbstractString, AbstractArray, or AbstractDict, you may need to dig further into StructUtil machinery to make your types serialize/deserialize as expected, but regular structs should "just work"
- Defining custom styles is meant to balance having to do some extra work (defining the style, passing it to
JSON.json
/JSON.parse
) with the power and flexibility of control over how JSON serialization/deserialization work for any type, owned or not
- What stayed the same:
Field Customization
- What stayed the same:
- Both allow renaming fields, excluding fields, and some control over manipulating fields from JSON output (keyword args, dateformats, etc.)
- What changed:
- StructUtils provides convenient "struct" macros (
@noarg
,@kwarg
,@tags
,@defaults
,@nonstruct
) that allow defining default values for fields, and specifying "field tags" which are named tuples of properties for fields. Via field tags, fields can customize naming, ignoring/excluding, dateformating, custom lowering/lifting, and even "type choosing" for abstract types.
- StructUtils provides convenient "struct" macros (
- Why the changes:
- The field tags and defaults of StructUtils provide very powerful and generalized abilities to specify properties for fields. These are integrated directly with the serialize/deserialize process of StructUtils and provide a seemless way to enhance and control fields as desired. Instead of providing numerous StructType overloads, we can annotate individual fields appropriately, keeping context and information tidy and close to the source.
- What stayed the same:
Null and Empty Value Handling
- What stayed the same:
- Both allow control over including/omitting null values and empty collections
- What changed:
- Control mechanism: JSON3.jl uses
StructTypes.omitempties
, JSON.jl 1.0 uses keyword argumentsomit_null
andomit_empty
; or struct-level overloads or annotations to control omission
- Control mechanism: JSON3.jl uses
- What stayed the same:
Reading JSON
JSON3.read
→JSON.parse
/JSON.lazy
- What stayed the same:
- Core functionality of parsing JSON into Julia values
- Support for typed parsing into custom structs
- Lazy parsing features
- What changed:
- Function names:
JSON3.read
becomes eitherJSON.parse
(eager) orJSON.lazy
(lazy) - Default container type:
JSON3.Object/JSON3.Array
becomesJSON.Object{String, Any}/Vector{Any}
- Type integration: JSON3.jl uses StructTypes.jl, JSON.jl 1.0 uses StructUtils.jl
- Lazy value access: Both use property access syntax (
obj.field
) but with slightly different semantics
- Function names:
- Migration examples:
# JSON3.jl obj = JSON3.read(json_str) typed_obj = JSON3.read(json_str, MyType) # JSON.jl 1.0 obj = JSON.parse(json_str) # eager parsing lazy_obj = JSON.lazy(json_str) # lazy parsing materialized = lazy_obj[] # materialize lazy value typed_obj = JSON.parse(json_str, MyType) # typed parsing
- What stayed the same:
Lazy Parsing
- What stayed the same:
- Both support lazy parsing for efficient access to parts of large JSON documents
- Both allow dot notation for accessing object fields
- What changed:
- Object types:
JSON3.Object
becomesJSON.LazyValue
with object type - Array indexing: Similar, but slight syntax differences for materializing values
- Materialization: In JSON3.jl specific values materialize when accessed, in JSON.jl 1.0 you explicitly use
[]
to materialize
- Object types:
- Migration examples:
# JSON3.jl obj = JSON3.read(json_str) value = obj.deeply.nested.field # value is materialized # JSON.jl 1.0 obj = JSON.lazy(json_str) lazy_value = obj.deeply.nested.field # still lazy value = obj.deeply.nested.field[] # now materialized
- Why the changes:
- The lazy support in JSON.jl is truly lazy and the underlying JSON is only parsed/navigated as explicitly requested. JSON3.jl still fully parsed the JSON into a fairly compact binary representation, avoiding full materialization of objects and arrays.
- What stayed the same:
Typed Parsing
- What stayed the same:
- Both allow parsing directly into custom types
- Both support object mapping, handling nested types, unions with Nothing/Missing
- What changed:
- Interface:
StructTypes.StructType
becomes JSON.jl's StructUtils integration - Default values:
StructTypes.defaults
becomes@defaults
macro - Type selection: Custom JSON3 dispatching becomes
JSON.@choosetype
- Interface:
- Migration examples:
# JSON3.jl StructTypes.StructType(::Type{MyType}) = StructTypes.Struct() StructTypes.defaults(::Type{MyType}) = (field1=0, field2="default") # Type selection in JSON3.jl StructTypes.StructType(::Type{AbstractParent}) = StructTypes.AbstractType() StructTypes.subtypes(::Type{AbstractParent}) = (a=ConcreteA, b=ConcreteB) # JSON.jl 1.0 @defaults struct MyType field1::Int = 0 field2::String = "default" end # Type selection in JSON.jl 1.0 JSON.@choosetype AbstractParent x -> x.type[] == "a" ? ConcreteA : ConcreteB
- What stayed the same:
Custom Field Mapping
- What stayed the same:
- Both support mapping between JSON property names and struct field names
- Both handle date formatting and other special types
- What changed:
- Interface: JSON3.jl uses
StructTypes.names
, JSON.jl 1.0 uses field tags - Date handling: Different formats for specifying date formats
- Interface: JSON3.jl uses
- Migration examples:
# JSON3.jl StructTypes.names(::Type{MyType}) = ((:json_name, :struct_field),) StructTypes.keywordargs(::Type{MyType}) = (date_field=(dateformat=dateformat"yyyy-mm-dd",),) # JSON.jl 1.0 @tags struct MyType struct_field::Int &(json=(name="json_name",),) date_field::Date &(json=(dateformat="yyyy-mm-dd",),) end
- What stayed the same:
Features unique to each library
Only in JSON3.jl:
- Struct Generation: The ability to automatically generate Julia struct definitions from JSON examples
# This functionality is not available in JSON.jl 1.0 struct_def = JSON3.generate_struct(json_data, "MyStruct")
- If you rely heavily on this feature, continue using JSON3.jl for this specific purpose until this functionality is migrated to a separate package
- Struct Generation: The ability to automatically generate Julia struct definitions from JSON examples
Only in JSON.jl 1.0:
- Enhanced JSON Lines Support: Better handling of JSON Lines format with auto-detection for files with
.jsonl
extension - More Float Formatting Controls: Additional options for float precision and format style
- Improved Circular Reference Handling: Better detection and handling of circular references
- Enhanced JSON Lines Support: Better handling of JSON Lines format with auto-detection for files with