Skip to content

Commit

Permalink
Getting Started Docs (#147)
Browse files Browse the repository at this point in the history
* docs: add getting started section, docstrings

* docs: fix pretty signature

* docs: add nan to allow_inf

Co-authored-by: Jacob Quinn <[email protected]>

* docs: add json relationships, polish doc strings, references

Co-authored-by: Jacob Quinn <[email protected]>
  • Loading branch information
mcmcgrath13 and quinnj authored Apr 21, 2021
1 parent c73edd7 commit 80c5bd4
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 8 deletions.
151 changes: 151 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,157 @@
Depth = 4
```

## Getting Started

> Yet another JSON package for Julia; this one is for speed and slick struct mapping
JSON3 provides two main functions: [`JSON3.read`](@ref) and [`JSON3.write`](@ref). These allow, in the basic case, reading a JSON string into a [`JSON3.Object`](@ref) or [`JSON3.Array`](@ref), which allow for dot or bracket indexing and can be copied into base `Dict`s or `Vector`s if needed. The slick struct mapping allows reading a JSON string directly into (almost) any type you wish and then writing directly from those types into JSON as well.

### Examples

#### Basic reading and writing
```@example
using JSON3 # hide
json_string = """{"a": 1, "b": "hello, world"}"""
hello_world = JSON3.read(json_string)
# can access the fields with dot or bracket notation
println(hello_world.b)
println(hello_world["a"])
JSON3.write(hello_world)
```

#### Write with pretty printing
```@example
using JSON3 # hide
json_string = """{"a": 1, "b": "hello, world"}"""
hello_world = JSON3.read(json_string)
JSON3.pretty(JSON3.write(hello_world))
```

#### Read and write from/to a file
```jl
json_string = read("my_file.json", String)

hello_world = JSON3.read(json_string)

open("my_new_file.json", "w") do io
JSON3.write(io, hello_world)
end
```

#### Read JSON into a type

See more details on the types that are provided and how to customize parsing [below](#Struct-API).

```@example
using JSON3 # hide
using StructTypes
json_string = """{"a": 1, "b": "hello, world"}"""
struct MyType
a::Int
b::String
end
StructTypes.StructType(::Type{MyType}) = StructTypes.Struct()
hello_world = JSON3.read(json_string, MyType)
println(hello_world)
JSON3.write(hello_world)
```

#### Read JSON into an already instantiated struct

```@example
using JSON3 # hide
using StructTypes
Base.@kwdef mutable struct MyType
a::Int = 0
b::String = ""
c::String = ""
end
StructTypes.StructType(::Type{MyType}) = StructTypes.Mutable()
t = MyType(c = "foo")
json_string = """{"a": 1, "b": "hello, world"}"""
JSON3.read!(json_string, t)
```

#### Generate a type from your JSON

See the [section on generating types](#Generated-Types) for more details.

```@example
using JSON3 # hide
using StructTypes
json_string = """{"a": 1, "b": "hello, world"}"""
JSON3.@generatetypes json_string
hello_world = JSON3.read(json_string, JSONTypes.Root)
```

#### Read in a date
```@example
using JSON3 # hide
using Dates
json_string = "\"2000-01-01\""
df = dateformat"yyyy-mm-dd"
my_date = JSON3.read(json_string, Date; dateformat=df)
```

#### Read a quoted number
```@example
using JSON3 # hide
using StructTypes
json_string = """{"a": "1", "b": "hello, world"}"""
struct MyType
a::Int
b::String
end
StructTypes.StructType(::Type{MyType}) = StructTypes.Struct()
hello_world = JSON3.read(json_string, MyType; parsequoted=true)
```

### API

```@docs
JSON3.read
JSON3.read!
JSON3.write
JSON3.pretty
JSON3.@pretty
JSON3.Object
JSON3.Array
Base.copy
```

### In Relation to Other JSON Packages

#### JSON.jl

While the [JSON.jl](https://github.com/JuliaIO/JSON.jl) package has been around since the very early days of Julia, JSON3.jl aims a faster core implementation of JSON parsing (via [`JSON3.read`](@ref)), as well as better integration with custom types using the [Struct API](#Struct-API). Via the [StructTypes.jl](https://github.com/JuliaData/StructTypes.jl) package, JSON3 provides numerous configurations for reading/writing custom types.

#### JSONTables.jl

[JSONTables.jl](https://github.com/JuliaData/JSONTables.jl) uses JSON3 under the hood to read and write JSON sources to/from [Tables.jl](https://github.com/JuliaData/Tables.jl) compatible tables.

## Builtin types

The JSON format is made up of just a few types: Object, Array, String, Number, Bool, and Null.
Expand Down
7 changes: 7 additions & 0 deletions src/JSON3.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module JSON3

using Parsers, Mmap, UUIDs, Dates, StructTypes

"""An immutable (read only) struct which provides an efficient view of a JSON object. Supports the `AbstractDict` interface. See [built in types](#Builtin-types) for more detail on why we have an `Object` type."""
struct Object{S <: AbstractVector{UInt8}, TT <: AbstractVector{UInt64}} <: AbstractDict{Symbol, Any}
buf::S
tape::TT
Expand All @@ -10,6 +11,7 @@ end

Object() = Object(codeunits(""), UInt64[object(Int64(2)), 0], Dict{Symbol, Int}())

"""An immutable (read only) struct which provides an efficient view of a JSON array. Supports the `AbstractArray` interface. See [built in types](#Builtin-types) for more detail on why we have an `Array` type."""
struct Array{T, S <: AbstractVector{UInt8}, TT <: AbstractVector{UInt64}} <: AbstractVector{T}
buf::S
tape::TT
Expand Down Expand Up @@ -124,6 +126,11 @@ Base.propertynames(obj::Object) = collect(keys(obj))
Base.getproperty(obj::Object, prop::Symbol) = get(obj, prop)
Base.getindex(obj::Object, key) = get(obj, key)

"""
copy(obj)
Recursively copy [`JSON3.Object`](@ref)s to `Dict`s and [`JSON3.Array`](@ref)s to `Vector`s. This copy can then be mutated if needed.
"""
function Base.copy(obj::Object)
dict = Dict{Symbol, Any}()
for (k, v) in obj
Expand Down
14 changes: 7 additions & 7 deletions src/gentypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ unify(a::Type{Vector{T}}, b::Type{Vector{S}}) where {T,S} = Vector{unify(T, S)}

# parse json into a type, maintain field order
"""
generate_type(json)
JSON3.generate_type(json)
Given a JSON3 Object or Array, return a "raw type" from it. A raw type is typically a `NamedTuple`, which can contain further nested `NamedTuples`, concrete, `Array`, or `Union` types.
"""
Expand Down Expand Up @@ -112,7 +112,7 @@ function pascalcase(s::Symbol)
end

"""
write_exprs(expr, f)
JSON3.write_exprs(expr, f)
Write an `Expr` or `Vector{Expr}` to file. Formatted so that it can be used with `include`.
"""
Expand All @@ -139,7 +139,7 @@ end

# entry function for turning a "raw" type from `generate_type` to Exprs
"""
generate_exprs(raw_type; root_name=:Root, mutable=true)
JSON3.generate_exprs(raw_type; root_name=:Root, mutable=true)
Generate a vector of `Expr` from a "raw_type". This will un-nest any sub-types within the root type.
Expand Down Expand Up @@ -227,7 +227,7 @@ end

# create a module with the struct declarations as well as the StructType declarations
"""
generate_struct_type_module(exprs, module_name)
JSON3.generate_struct_type_module(exprs, module_name)
Given a vector of `exprs` (output of [`generate_exprs`](@ref)), return an `Expr` containing the AST for a module with name `module_name`. The module will map all types to the appropriate `StructType`, so the result can immediately used with `JSON3.read(json, T)`.
"""
Expand All @@ -247,7 +247,7 @@ function generate_struct_type_module(exprs, module_name)
end

"""
generatetypes(json, module_name; mutable=true, root_name=:Root)
JSON3.generatetypes(json, module_name; mutable=true, root_name=:Root)
Convenience function to go from a json string or file name to an AST with a module of structs.
Expand Down Expand Up @@ -281,7 +281,7 @@ end

# macro to create a module with types generated from a json string
"""
@generatetypes json [module_name]
JSON3.@generatetypes json [module_name]
Evaluate the result of the [`generatetypes`](@ref) function in the current scope.
"""
Expand All @@ -295,7 +295,7 @@ end

# convenience function to go from json_string, to file with module
"""
writetypes(json, file_name; module_name=:JSONTypes, root_name=:Root, mutable=true)
JSON3.writetypes(json, file_name; module_name=:JSONTypes, root_name=:Root, mutable=true)
Write the result of the [`generatetypes`](@ref) function to file.
"""
Expand Down
22 changes: 21 additions & 1 deletion src/pretty.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
"""
JSON3.@pretty json_str
Pretty print a JSON string or an object as JSON.
"""
macro pretty(json)
return esc(:(JSON3.pretty($json)))
end

"""
JSON3.pretty(x; kw...)
JSON3.pretty(io, x; kw...)
Pretty print a JSON string.
## Args
* `x`: A JSON string, or an object to write to JSON then pretty print.
* `io`: The `IO` object to write the pretty printed string to. [default `stdout`]
## Keyword Args
See [`JSON3.write`](@ref) and [`JSON3.read`](@ref).
"""
pretty(str; kw...) = pretty(stdout, str; kw...)
pretty(out::IO, x) = pretty(out, JSON3.write(x))
pretty(out::IO, x; kw...) = pretty(out, JSON3.write(x; kw...); kw...)
function pretty(out::IO, str::String, indent=0, offset=0; kw...)
buf = codeunits(str)
len = length(buf)
Expand Down
17 changes: 17 additions & 0 deletions src/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ Base.codeunits(x::VectorString) = x.bytes
read(io::Union{IO, Base.AbstractCmd}; kw...) = read(Base.read(io, String); kw...)
read(bytes::AbstractVector{UInt8}; kw...) = read(VectorString(bytes); kw...)

"""
JSON3.read(json_str, [type]; kw... )
Read JSON.
## Args
* `json_str`: A string, IO, or bytes (`AbstractVector{UInt8`) containing JSON to read
* `type`: Optionally, a type to read the JSON into. If not a [built in type](#Builtin-types), must have a "struct mapping" registered with [StructTypes.jl](#Struct-API).
## Keyword Args
* `jsonlines`: A Bool indicating that the `json_str` contains newline delimited JSON strings, which will be read into a `JSON3.Array` of the JSON values. See [jsonlines](https://jsonlines.org/) for reference. [default `false`]
* `allow_inf`: Allow reading of `Inf` and `NaN` values (not part of the JSON standard). [default `false`]
* `dateformat`: A [`DateFormat`](https://docs.julialang.org/en/v1/stdlib/Dates/#Dates.DateFormat) describing the format of dates in the JSON so that they can be read into `Date`s, `Time`s, or `DateTime`s when reading into a type. [default `Dates.default_format(T)`]
* `parsequoted`: When reading into a NumberType, parse quoted values into numbers instead of as stings. [default `false`]
"""
function read(str::AbstractString; jsonlines::Bool=false, kw...)
buf = codeunits(str)
len = length(buf)
Expand Down
5 changes: 5 additions & 0 deletions src/structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ function read(str::AbstractString, ::Type{T}; kw...) where {T}
return x
end

"""
JSON3.read!(json_str, x; kw...)
Incrementally update an instance of a mutable object `x` with the contents of `json_str`. See [`JSON3.read`](@ref) for more details.
"""
function read!(str::AbstractString, x::T; kw...) where {T}
buf, pos, len, b = _prepare_read(str, T)
pos, x = read!(StructType(T), buf, pos, len, b, T, x; kw...)
Expand Down
15 changes: 15 additions & 0 deletions src/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ defaultminimum(x::Union{Tuple, AbstractSet, AbstractArray}) = isempty(x) ? 2 : s
defaultminimum(x::Union{AbstractDict, NamedTuple, Pair}) = isempty(x) ? 2 : sum(defaultminimum(k) + defaultminimum(v) for (k, v) in StructTypes.keyvaluepairs(x))
defaultminimum(x) = max(2, sizeof(x))

"""
JSON3.write([io], x; kw...)
Write JSON.
## Args
* `io`: Optionally, an `IO` object to write the resulting JSON string to. By default, will return a `String` if `io` is not provided.
* `x`: An object to serialize as JSON. If not a [built in type](#Builtin-types), must have a "struct mapping" registered with [StructTypes.jl](#Struct-API).
## Keyword Args
* `allow_inf`: Allow writing of `Inf` and `NaN` values (not part of the JSON standard). [default `false`]
* `dateformat`: A [`DateFormat`](https://docs.julialang.org/en/v1/stdlib/Dates/#Dates.DateFormat) describing how to format `Date`s in the object. [default `Dates.default_format(T)`]
"""
function write(io::IO, obj::T; kw...) where {T}
len = defaultminimum(obj)
buf = Base.StringVector(len)
Expand Down

0 comments on commit 80c5bd4

Please sign in to comment.