jli  Linuxx86_641.10.3v1.10.30b4590a5507d3f3046e5bafc007cacbbfc9b310bhG#JSONTjޠ,hyϱB5+E1E*/opt/julia/packages/JSON/93Ea8/src/JSON.jlA,/opt/julia/packages/JSON/93Ea8/src/Common.jlArP>I>NUnicodeCommon+/opt/julia/packages/JSON/93Ea8/src/bytes.jlACommon,/opt/julia/packages/JSON/93Ea8/src/errors.jlACommon,/opt/julia/packages/JSON/93Ea8/src/Parser.jlAxg,OP~:MmapParser-+YPi iParsersParser1/opt/julia/packages/JSON/93Ea8/src/specialized.jlAParser4/opt/julia/packages/JSON/93Ea8/src/Serializations.jlA,/opt/julia/packages/JSON/93Ea8/src/Writer.jlAj2 EY8pDatesWriterrP>I>NUnicodeWriter CoremуJ5Basemу]J5MainmуJ5ArgToolsBń x(mуF K5 Artifactsmr-V3|mу K5Base64UlD*_mу> K5CRC32c\y.jmуj K5 FileWatchingXzsy`{,zmуh& K5LibdluVW59˗,mу-" K5LoggingT{VhUXM=mуrU" K5MmapP~:xg,Omу|' K5NetworkOptionsC0YW,mуʠ, K5SHAQ<$!<%mу1 K5 Serialization [)*k1mу-G K5Sockets1V$ bdސݗmуYBY K5UnicodeP>I>Nrmуeszo K5 LinearAlgebraSm7̏mуuux K5 OpenBLAS_jll[(Śb6EcQ FmуDux K5libblastrampoline_jllLSۆ }lxӠmу^} K5MarkdownZPn7z`smу/Ed~ K5Printfg^cX׸QDmу;h K5Random_ɢ?\Ymу? K5TarOi>աmу!t, K5DatesEY8pj2 mуX K5FuturebS;3{I xVMmуsD K5InteractiveUtilsWL ~@'ZmуVg K5LibGit2Z[&RPTv3EКRmу8J K5 LibGit2_jll YXg}]$mуD K5 MbedTLS_jllAX 3ȡ_mу- K5 LibSSH2_jlloTZk)߆ STRING_DELIM, BACKSLASH => BACKSLASH, SOLIDUS => SOLIDUS, LATIN_B => BACKSPACE, LATIN_F => FORM_FEED, LATIN_N => NEWLINE, LATIN_R => RETURN, LATIN_T => TAB) const REVERSE_ESCAPES = Dict(reverse(p) for p in ESCAPES) const ESCAPED_ARRAY = Vector{Vector{UInt8}}(undef, 256) for c in 0x00:0xFF ESCAPED_ARRAY[c + 1] = if c == SOLIDUS [SOLIDUS] # don't escape this one elseif c ≥ 0x80 [c] # UTF-8 character copied verbatim elseif haskey(REVERSE_ESCAPES, c) [BACKSLASH, REVERSE_ESCAPES[c]] elseif iscntrl(Char(c)) || !isprint(Char(c)) UInt8[BACKSLASH, LATIN_U, string(c, base=16, pad=4)...] else [c] end end export BACKSPACE, TAB, NEWLINE, FORM_FEED, RETURN, SPACE, STRING_DELIM, PLUS_SIGN, DELIMITER, MINUS_SIGN, DECIMAL_POINT, SOLIDUS, DIGIT_ZERO, DIGIT_NINE, SEPARATOR, LATIN_UPPER_A, LATIN_UPPER_E, LATIN_UPPER_F, LATIN_UPPER_I, LATIN_UPPER_N, ARRAY_BEGIN, BACKSLASH, ARRAY_END, LATIN_A, LATIN_B, LATIN_E, LATIN_F, LATIN_I, LATIN_L, LATIN_N, LATIN_R, LATIN_S, LATIN_T, LATIN_U, LATIN_Y, OBJECT_BEGIN, OBJECT_END, ESCAPES, REVERSE_ESCAPES, ESCAPED_ARRAY ,/opt/julia/packages/JSON/93Ea8/src/errors.jlZ# The following errors may be thrown by the parser const E_EXPECTED_EOF = "Expected end of input" const E_UNEXPECTED_EOF = "Unexpected end of input" const E_UNEXPECTED_CHAR = "Unexpected character" const E_BAD_KEY = "Invalid object key" const E_BAD_ESCAPE = "Invalid escape sequence" const E_BAD_CONTROL = "ASCII control character in string" const E_LEADING_ZERO = "Invalid leading zero in number" const E_BAD_NUMBER = "Invalid number" export E_EXPECTED_EOF, E_UNEXPECTED_EOF, E_UNEXPECTED_CHAR, E_BAD_KEY, E_BAD_ESCAPE, E_BAD_CONTROL, E_LEADING_ZERO, E_BAD_NUMBER ,/opt/julia/packages/JSON/93Ea8/src/Parser.jl6=module Parser # JSON using Mmap using ..Common import Parsers """ Like `isspace`, but work on bytes and includes only the four whitespace characters defined by the JSON standard: space, tab, line feed, and carriage return. """ isjsonspace(b::UInt8) = b == SPACE || b == TAB || b == NEWLINE || b == RETURN """ Like `isdigit`, but for bytes. """ isjsondigit(b::UInt8) = DIGIT_ZERO ≤ b ≤ DIGIT_NINE abstract type ParserState end mutable struct MemoryParserState <: ParserState utf8::String s::Int end # it is convenient to access MemoryParserState like a Vector{UInt8} to avoid copies Base.@propagate_inbounds Base.getindex(state::MemoryParserState, i::Int) = codeunit(state.utf8, i) Base.length(state::MemoryParserState) = sizeof(state.utf8) mutable struct StreamingParserState{T <: IO} <: ParserState io::T cur::UInt8 used::Bool utf8array::Vector{UInt8} end StreamingParserState(io::IO) = StreamingParserState(io, 0x00, true, UInt8[]) struct ParserContext{DictType, IntType, AllowNanInf, NullValue} end """ Return the byte at the current position of the `ParserState`. If there is no byte (that is, the `ParserState` is done), then an error is thrown that the input ended unexpectedly. """ @inline function byteat(ps::MemoryParserState) @inbounds if hasmore(ps) return ps[ps.s] else _error(E_UNEXPECTED_EOF, ps) end end @inline function byteat(ps::StreamingParserState) if ps.used ps.used = false if eof(ps.io) _error(E_UNEXPECTED_EOF, ps) else ps.cur = read(ps.io, UInt8) end end ps.cur end """ Like `byteat`, but with no special bounds check and error message. Useful when a current byte is known to exist. """ @inline current(ps::MemoryParserState) = ps[ps.s] @inline current(ps::StreamingParserState) = byteat(ps) """ Require the current byte of the `ParserState` to be the given byte, and then skip past that byte. Otherwise, an error is thrown. """ @inline function skip!(ps::ParserState, c::UInt8) if byteat(ps) == c incr!(ps) else _error_expected_char(c, ps) end end @noinline _error_expected_char(c, ps) = _error("Expected '$(Char(c))' here", ps) function skip!(ps::ParserState, cs::UInt8...) for c in cs skip!(ps, c) end end """ Move the `ParserState` to the next byte. """ @inline incr!(ps::MemoryParserState) = (ps.s += 1) @inline incr!(ps::StreamingParserState) = (ps.used = true) """ Move the `ParserState` to the next byte, and return the value at the byte before the advancement. If the `ParserState` is already done, then throw an error. """ @inline advance!(ps::ParserState) = (b = byteat(ps); incr!(ps); b) """ Return `true` if there is a current byte, and `false` if all bytes have been exausted. """ @inline hasmore(ps::MemoryParserState) = ps.s ≤ length(ps) @inline hasmore(ps::StreamingParserState) = true # no more now ≠ no more ever """ Remove as many whitespace bytes as possible from the `ParserState` starting from the current byte. """ @inline function chomp_space!(ps::ParserState) @inbounds while hasmore(ps) && isjsonspace(current(ps)) incr!(ps) end end # Used for line counts function _count_before(haystack::AbstractString, needle::Char, _end::Int) count = 0 for (i,c) in enumerate(haystack) i >= _end && return count count += c == needle end return count end # Throws an error message with an indicator to the source @noinline function _error(message::AbstractString, ps::MemoryParserState) orig = ps.utf8 lines = _count_before(orig, '\n', ps.s) # Replace all special multi-line/multi-space characters with a space. strnl = replace(orig, r"[\b\f\n\r\t\s]" => " ") li = (ps.s > 20) ? ps.s - 9 : 1 # Left index ri = min(lastindex(orig), ps.s + 20) # Right index error(message * "\nLine: " * string(lines) * "\nAround: ..." * strnl[li:ri] * "..." * "\n " * (" " ^ (ps.s - li)) * "^\n" ) end @noinline function _error(message::AbstractString, ps::StreamingParserState) error("$message\n ...when parsing byte with value '$(current(ps))'") end # PARSING """ Given a `ParserState`, after possibly any amount of whitespace, return the next parseable value. """ function parse_value(pc::ParserContext, ps::ParserState) chomp_space!(ps) @inbounds byte = byteat(ps) if byte == STRING_DELIM parse_string(ps) elseif isjsondigit(byte) || byte == MINUS_SIGN parse_number(pc, ps) elseif byte == OBJECT_BEGIN parse_object(pc, ps) elseif byte == ARRAY_BEGIN parse_array(pc, ps) else parse_jsconstant(pc, ps) end end function parse_jsconstant(::ParserContext{<:Any,<:Any,AllowNanInf,NullValue}, ps::ParserState) where {AllowNanInf,NullValue} c = advance!(ps) if c == LATIN_T # true skip!(ps, LATIN_R, LATIN_U, LATIN_E) true elseif c == LATIN_F # false skip!(ps, LATIN_A, LATIN_L, LATIN_S, LATIN_E) false elseif c == LATIN_N # null skip!(ps, LATIN_U, LATIN_L, LATIN_L) NullValue elseif AllowNanInf && c == LATIN_UPPER_N skip!(ps, LATIN_A, LATIN_UPPER_N) NaN elseif AllowNanInf && c == LATIN_UPPER_I skip!(ps, LATIN_N, LATIN_F, LATIN_I, LATIN_N, LATIN_I, LATIN_T, LATIN_Y) Inf else _error(E_UNEXPECTED_CHAR, ps) end end function parse_array(pc::ParserContext, ps::ParserState) result = Any[] @inbounds incr!(ps) # Skip over opening '[' chomp_space!(ps) if byteat(ps) ≠ ARRAY_END # special case for empty array @inbounds while true push!(result, parse_value(pc, ps)) chomp_space!(ps) byteat(ps) == ARRAY_END && break skip!(ps, DELIMITER) end end @inbounds incr!(ps) result end function parse_object(pc::ParserContext{DictType,<:Real,<:Any}, ps::ParserState) where DictType obj = DictType() keyT = keytype(typeof(obj)) incr!(ps) # Skip over opening '{' chomp_space!(ps) if byteat(ps) ≠ OBJECT_END # special case for empty object @inbounds while true # Read key chomp_space!(ps) byteat(ps) == STRING_DELIM || _error(E_BAD_KEY, ps) key = parse_string(ps) chomp_space!(ps) skip!(ps, SEPARATOR) # Read value value = parse_value(pc, ps) chomp_space!(ps) obj[keyT === Symbol ? Symbol(key) : convert(keyT, key)] = value byteat(ps) == OBJECT_END && break skip!(ps, DELIMITER) end end incr!(ps) obj end utf16_is_surrogate(c::UInt16) = (c & 0xf800) == 0xd800 utf16_get_supplementary(lead::UInt16, trail::UInt16) = Char(UInt32(lead-0xd7f7)<<10 + trail) function read_four_hex_digits!(ps::ParserState) local n::UInt16 = 0 for _ in 1:4 b = advance!(ps) n = n << 4 + if isjsondigit(b) b - DIGIT_ZERO elseif LATIN_A ≤ b ≤ LATIN_F b - (LATIN_A - UInt8(10)) elseif LATIN_UPPER_A ≤ b ≤ LATIN_UPPER_F b - (LATIN_UPPER_A - UInt8(10)) else _error(E_BAD_ESCAPE, ps) end end n end function read_unicode_escape!(ps) u1 = read_four_hex_digits!(ps) if utf16_is_surrogate(u1) skip!(ps, BACKSLASH) skip!(ps, LATIN_U) u2 = read_four_hex_digits!(ps) utf16_get_supplementary(u1, u2) else Char(u1) end end function parse_string(ps::ParserState) b = IOBuffer() incr!(ps) # skip opening quote while true c = advance!(ps) if c == BACKSLASH c = advance!(ps) if c == LATIN_U # Unicode escape write(b, read_unicode_escape!(ps)) else c = get(ESCAPES, c, 0x00) c == 0x00 && _error(E_BAD_ESCAPE, ps) write(b, c) end continue elseif c < SPACE _error(E_BAD_CONTROL, ps) elseif c == STRING_DELIM return String(take!(b)) end write(b, c) end end """ Return `true` if the given bytes vector, starting at `from` and ending at `to`, has a leading zero. """ function hasleadingzero(bytes, from::Int, to::Int) c = bytes[from] from + 1 < to && c == UInt8('-') && bytes[from + 1] == DIGIT_ZERO && isjsondigit(bytes[from + 2]) || from < to && to > from + 1 && c == DIGIT_ZERO && isjsondigit(bytes[from + 1]) end """ Parse a float from the given bytes vector, starting at `from` and ending at the byte before `to`. Bytes enclosed should all be ASCII characters. """ float_from_bytes(bytes::MemoryParserState, from::Int, to::Int) = float_from_bytes(bytes.utf8, from, to) function float_from_bytes(bytes::Union{String, Vector{UInt8}}, from::Int, to::Int)::Union{Float64,Nothing} return Parsers.tryparse(Float64, bytes isa String ? SubString(bytes, from:to) : view(bytes, from:to)) end """ Parse an integer from the given bytes vector, starting at `from` and ending at the byte before `to`. Bytes enclosed should all be ASCII characters. """ function int_from_bytes(pc::ParserContext{<:Any,IntType,<:Any}, ps::ParserState, bytes, from::Int, to::Int) where IntType <: Real @inbounds isnegative = bytes[from] == MINUS_SIGN ? (from += 1; true) : false num = IntType(0) @inbounds for i in from:to c = bytes[i] dig = c - DIGIT_ZERO if dig < 0x10 num = IntType(10) * num + IntType(dig) else _error(E_BAD_NUMBER, ps) end end ifelse(isnegative, -num, num) end function number_from_bytes(pc::ParserContext, ps::ParserState, isint::Bool, bytes, from::Int, to::Int) @inbounds if hasleadingzero(bytes, from, to) _error(E_LEADING_ZERO, ps) end if isint @inbounds if to == from && bytes[from] == MINUS_SIGN _error(E_BAD_NUMBER, ps) end int_from_bytes(pc, ps, bytes, from, to) else res = float_from_bytes(bytes, from, to) res === nothing ? _error(E_BAD_NUMBER, ps) : res end end function parse_number(pc::ParserContext{<:Any,<:Any,AllowNanInf}, ps::ParserState) where AllowNanInf # Determine the end of the floating point by skipping past ASCII values # 0-9, +, -, e, E, and . number = ps.utf8array isint = true negative = false c = current(ps) # Parse and keep track of initial minus sign (for parsing -Infinity) if AllowNanInf && c == MINUS_SIGN push!(number, UInt8(c)) # save in case the next character is a number negative = true incr!(ps) end @inbounds while hasmore(ps) c = current(ps) if isjsondigit(c) || c == MINUS_SIGN push!(number, UInt8(c)) elseif c in (PLUS_SIGN, LATIN_E, LATIN_UPPER_E, DECIMAL_POINT) push!(number, UInt8(c)) isint = false elseif AllowNanInf && c == LATIN_UPPER_I infinity = parse_jsconstant(pc, ps) resize!(number, 0) return (negative ? -infinity : infinity) else break end incr!(ps) end v = number_from_bytes(pc, ps, isint, number, 1, length(number)) resize!(number, 0) return v end unparameterize_type(x) = x # Fallback for nontypes -- functions etc function unparameterize_type(T::Type) candidate = typeintersect(T, AbstractDict{String, Any}) candidate <: Union{} ? T : candidate end # Workaround for slow dynamic dispatch for creating objects const DEFAULT_PARSERCONTEXT = ParserContext{Dict{String, Any}, Int64, false, nothing}() function _get_parsercontext(dicttype, inttype, allownan, null) if dicttype == Dict{String, Any} && inttype == Int64 && !allownan DEFAULT_PARSERCONTEXT else ParserContext{unparameterize_type(dicttype), inttype, allownan, null}.instance end end """ parse(str::AbstractString; dicttype::Type{T}=Dict, inttype::Type{<:Real}=Int64, allownan::Bool=true, null=nothing) where {T<:AbstractDict} Parses the given JSON string into corresponding Julia types. Keyword arguments: • dicttype: Associative type to use when parsing JSON objects (default: Dict{String, Any}) • inttype: Real number type to use when parsing JSON numbers that can be parsed as integers (default: Int64) • allownan: allow parsing of NaN, Infinity, and -Infinity (default: true) • null: value to use for parsed JSON `null` values (default: `nothing`) """ function parse(str::AbstractString; dicttype=Dict{String,Any}, inttype::Type{<:Real}=Int64, allownan::Bool=true, null=nothing) pc = _get_parsercontext(dicttype, inttype, allownan, null) ps = MemoryParserState(str, 1) v = parse_value(pc, ps) chomp_space!(ps) if hasmore(ps) _error(E_EXPECTED_EOF, ps) end v end """ parse(io::IO; dicttype::Type{T}=Dict, inttype::Type{<:Real}=Int64, allownan=true, null=nothing) where {T<:AbstractDict} Parses JSON from the given IO stream into corresponding Julia types. Keyword arguments: • dicttype: Associative type to use when parsing JSON objects (default: Dict{String, Any}) • inttype: Real number type to use when parsing JSON numbers that can be parsed as integers (default: Int64) • allownan: allow parsing of NaN, Infinity, and -Infinity (default: true) • null: value to use for parsed JSON `null` values (default: `nothing`) """ function parse(io::IO; dicttype=Dict{String,Any}, inttype::Type{<:Real}=Int64, allownan::Bool=true, null=nothing) pc = _get_parsercontext(dicttype, inttype, allownan, null) ps = StreamingParserState(io) parse_value(pc, ps) end """ parsefile(filename::AbstractString; dicttype=Dict{String, Any}, inttype::Type{<:Real}=Int64, allownan::Bool=true, null=nothing, use_mmap::Bool=true) Convenience function to parse JSON from the given file into corresponding Julia types. Keyword arguments: • dicttype: Associative type to use when parsing JSON objects (default: Dict{String, Any}) • inttype: Real number type to use when parsing JSON numbers that can be parsed as integers (default: Int64) • allownan: allow parsing of NaN, Infinity, and -Infinity (default: true) • null: value to use for parsed JSON `null` values (default: `nothing`) • use_mmap: use mmap when opening the file (default: true) """ function parsefile(filename::AbstractString; dicttype=Dict{String, Any}, inttype::Type{<:Real}=Int64, null=nothing, allownan::Bool=true, use_mmap::Bool=true) sz = filesize(filename) open(filename) do io s = use_mmap ? String(Mmap.mmap(io, Vector{UInt8}, sz)) : read(io, String) parse(s; dicttype=dicttype, inttype=inttype, allownan=allownan, null=null) end end # Efficient implementations of some of the above for in-memory parsing include("specialized.jl") end # module Parser 1/opt/julia/packages/JSON/93Ea8/src/specialized.jl!function maxsize_buffer(maxsize::Int) IOBuffer(maxsize=maxsize) end # Specialized functions for increased performance when JSON is in-memory function parse_string(ps::MemoryParserState) # "Dry Run": find length of string so we can allocate the right amount of # memory from the start. Does not do full error checking. fastpath, len = predict_string(ps) # Now read the string itself: # Fast path occurs when the string has no escaped characters. This is quite # often the case in real-world data, especially when keys are short strings. # We can just copy the data from the buffer in this case. if fastpath s = ps.s ps.s = s + len + 2 # byte after closing quote return unsafe_string(pointer(ps.utf8)+s, len) else String(take!(parse_string(ps, maxsize_buffer(len)))) end end """ Scan through a string at the current parser state and return a tuple containing information about the string. This function avoids memory allocation where possible. The first element of the returned tuple is a boolean indicating whether the string may be copied directly from the parser state. Special casing string parsing when there are no escaped characters leads to substantially increased performance in common situations. The second element of the returned tuple is an integer representing the exact length of the string, in bytes when encoded as UTF-8. This information is useful for pre-sizing a buffer to contain the parsed string. This function will throw an error if: - invalid control characters are found - an invalid unicode escape is read - the string is not terminated No error is thrown when other invalid backslash escapes are encountered. """ function predict_string(ps::MemoryParserState) e = length(ps) fastpath = true # true if no escapes in this string, so it can be copied len = 0 # the number of UTF8 bytes the string contains s = ps.s + 1 # skip past opening string character " @inbounds while s <= e c = ps[s] if c == BACKSLASH fastpath = false (s += 1) > e && break if ps[s] == LATIN_U # Unicode escape t = ps.s ps.s = s + 1 len += write(devnull, read_unicode_escape!(ps)) s = ps.s ps.s = t continue end elseif c == STRING_DELIM return fastpath, len elseif c < SPACE ps.s = s _error(E_BAD_CONTROL, ps) end len += 1 s += 1 end ps.s = s _error(E_UNEXPECTED_EOF, ps) end """ Parse the string starting at the parser state’s current location into the given pre-sized IOBuffer. The only correctness checking is for escape sequences, so the passed-in buffer must exactly represent the amount of space needed for parsing. """ function parse_string(ps::MemoryParserState, b::IOBuffer) s = ps.s e = length(ps) s += 1 # skip past opening string character " len = b.maxsize @inbounds while b.size < len c = ps[s] if c == BACKSLASH s += 1 s > e && break c = ps[s] if c == LATIN_U # Unicode escape ps.s = s + 1 write(b, read_unicode_escape!(ps)) s = ps.s continue else c = get(ESCAPES, c, 0x00) if c == 0x00 ps.s = s _error(E_BAD_ESCAPE, ps) end end end # UTF8-encoded non-ascii characters will be copied verbatim, which is # the desired behaviour write(b, c) s += 1 end # don't worry about non-termination or other edge cases; those should have # been caught in the dry run. ps.s = s + 1 b end function parse_number(pc::ParserContext{<:Any,<:Any,AllowNanInf}, ps::MemoryParserState) where AllowNanInf s = p = ps.s e = length(ps) isint = true negative = false @inbounds c = ps[p] # Parse and keep track of initial minus sign (for parsing -Infinity) if AllowNanInf && c == MINUS_SIGN negative = true p += 1 end # Determine the end of the floating point by skipping past ASCII values # 0-9, +, -, e, E, and . while p ≤ e @inbounds c = ps[p] if isjsondigit(c) || MINUS_SIGN == c # no-op elseif PLUS_SIGN == c || LATIN_E == c || LATIN_UPPER_E == c || DECIMAL_POINT == c isint = false elseif AllowNanInf && LATIN_UPPER_I == c ps.s = p infinity = parse_jsconstant(pc, ps) return (negative ? -infinity : infinity) else break end p += 1 end ps.s = p number_from_bytes(pc, ps, isint, ps, s, p - 1) end 4/opt/julia/packages/JSON/93Ea8/src/Serializations.jl""" JSON writer serialization contexts. This module defines the `Serialization` abstract type and several concrete implementations, as they relate to JSON. """ module Serializations using ..Common """ A `Serialization` defines how objects are lowered to JSON format. """ abstract type Serialization end """ The `CommonSerialization` comes with a default set of rules for serializing Julia types to their JSON equivalents. Additional rules are provided either by packages explicitly defining `JSON.show_json` for this serialization, or by the `JSON.lower` method. Most concrete implementations of serializers should subtype `CommonSerialization`, unless it is desirable to bypass the `lower` system, in which case `Serialization` should be subtyped. """ abstract type CommonSerialization <: Serialization end """ The `StandardSerialization` defines a common, standard JSON serialization format that is optimized to: - strictly follow the JSON standard - be useful in the greatest number of situations All serializations defined for `CommonSerialization` are inherited by `StandardSerialization`. It is therefore generally advised to add new serialization behaviour to `CommonSerialization`. """ struct StandardSerialization <: CommonSerialization end end ,/opt/julia/packages/JSON/93Ea8/src/Writer.jl/module Writer using Dates using ..Common using ..Serializations: Serialization, StandardSerialization, CommonSerialization using Unicode """ Internal JSON.jl implementation detail; do not depend on this type. A JSON primitive that wraps around any composite type to enable `Dict`-like serialization. """ struct CompositeTypeWrapper{T} wrapped::T fns::Vector{Symbol} end CompositeTypeWrapper(x, syms) = CompositeTypeWrapper(x, collect(syms)) CompositeTypeWrapper(x) = CompositeTypeWrapper(x, propertynames(x)) """ lower(x) Return a value of a JSON-encodable primitive type that `x` should be lowered into before encoding as JSON. Supported types are: `AbstractDict` and `NamedTuple` to JSON objects, `Tuple` and `AbstractVector` to JSON arrays, `AbstractArray` to nested JSON arrays, `AbstractString`, `Symbol`, `Enum`, or `Char` to JSON string, `Integer` and `AbstractFloat` to JSON number, `Bool` to JSON boolean, and `Nothing` to JSON null, or any other types with a `show_json` method defined. Extensions of this method should preserve the property that the return value is one of the aforementioned types. If first lowering to some intermediate type is required, then extensions should call `lower` before returning a value. Note that the return value need not be *recursively* lowered—this function may for instance return an `AbstractArray{Any, 1}` whose elements are not JSON primitives. """ function lower(a) if nfields(a) > 0 CompositeTypeWrapper(a) else error("Cannot serialize type $(typeof(a))") end end # To avoid allocating an intermediate string, we directly define `show_json` # for this type instead of lowering it to a string first (which would # allocate). However, the `show_json` method does call `lower` so as to allow # users to change the lowering of their `Enum` or even `AbstractString` # subtypes if necessary. const IsPrintedAsString = Union{ Dates.TimeType, Char, Type, AbstractString, Enum, Symbol} lower(x::IsPrintedAsString) = x lower(m::Module) = throw(ArgumentError("cannot serialize Module $m as JSON")) lower(x::Real) = convert(Float64, x) lower(x::Base.AbstractSet) = collect(x) """ Abstract supertype of all JSON and JSON-like structural writer contexts. """ abstract type StructuralContext <: IO end """ Internal implementation detail. A JSON structural context around an `IO` object. Structural writer contexts define the behaviour of serializing JSON structural objects, such as objects, arrays, and strings to JSON. The translation of Julia types to JSON structural objects is not handled by a `JSONContext`, but by a `Serialization` wrapper around it. Abstract supertype of `PrettyContext` and `CompactContext`. Data can be written to a JSON context in the usual way, but often higher-level operations such as `begin_array` or `begin_object` are preferred to directly writing bytes to the stream. """ abstract type JSONContext <: StructuralContext end """ Internal implementation detail. To handle recursive references in objects/arrays when writing, by default we want to track references to objects seen and break recursion cycles to avoid stack overflows. Subtypes of `RecursiveCheckContext` must include two fields in order to allow recursive cycle checking to work properly when writing: * `objectids::Set{UInt64}`: set of object ids in the current stack of objects being written * `recursive_cycle_token::Any`: Any string, `nothing`, or object to be written when a cycle is detected """ abstract type RecursiveCheckContext <: JSONContext end """ Internal implementation detail. Keeps track of the current location in the array or object, which winds and unwinds during serialization. """ mutable struct PrettyContext{T<:IO} <: RecursiveCheckContext io::T step::Int # number of spaces to step state::Int # number of steps at present first::Bool # whether an object/array was just started objectids::Set{UInt64} recursive_cycle_token end PrettyContext(io::IO, step, recursive_cycle_token=nothing) = PrettyContext(io, step, 0, false, Set{UInt64}(), recursive_cycle_token) """ Internal implementation detail. For compact printing, which in JSON is fully recursive. """ mutable struct CompactContext{T<:IO} <: RecursiveCheckContext io::T first::Bool objectids::Set{UInt64} recursive_cycle_token end CompactContext(io::IO, recursive_cycle_token=nothing) = CompactContext(io, false, Set{UInt64}(), recursive_cycle_token) """ Internal implementation detail. Implements an IO context safe for printing into JSON strings. """ struct StringContext{T<:IO} <: IO io::T end # These aliases make defining additional methods on `show_json` easier. const CS = CommonSerialization const SC = StructuralContext # Low-level direct access Base.write(io::JSONContext, byte::UInt8) = write(io.io, byte) Base.write(io::StringContext, byte::UInt8) = write(io.io, ESCAPED_ARRAY[byte + 1]) #= turn on if there's a performance benefit write(io::StringContext, char::Char) = char <= '\x7f' ? write(io, ESCAPED_ARRAY[UInt8(c) + 1]) : Base.print(io, c) =# """ indent(io::StructuralContext) If appropriate, write a newline to the given context, then indent it by the appropriate number of spaces. Otherwise, do nothing. """ @inline function indent(io::PrettyContext) write(io, NEWLINE) for _ in 1:io.state write(io, SPACE) end end @inline indent(io::CompactContext) = nothing """ separate(io::StructuralContext) Write a colon, followed by a space if appropriate, to the given context. """ @inline separate(io::PrettyContext) = write(io, SEPARATOR, SPACE) @inline separate(io::CompactContext) = write(io, SEPARATOR) """ delimit(io::StructuralContext) If this is not the first item written in a collection, write a comma in the structural context. Otherwise, do not write a comma, but set a flag that the first element has been written already. """ @inline function delimit(io::JSONContext) if !io.first write(io, DELIMITER) end io.first = false end for kind in ("object", "array") beginfn = Symbol("begin_", kind) beginsym = Symbol(uppercase(kind), "_BEGIN") endfn = Symbol("end_", kind) endsym = Symbol(uppercase(kind), "_END") # Begin and end objects @eval function $beginfn(io::PrettyContext) write(io, $beginsym) io.state += io.step io.first = true end @eval $beginfn(io::CompactContext) = (write(io, $beginsym); io.first = true) @eval function $endfn(io::PrettyContext) io.state -= io.step if !io.first indent(io) end write(io, $endsym) io.first = false end @eval $endfn(io::CompactContext) = (write(io, $endsym); io.first = false) end """ show_string(io::IO, str) Print `str` as a JSON string (that is, properly escaped and wrapped by double quotes) to the given IO object `io`. """ function show_string(io::IO, x) write(io, STRING_DELIM) Base.print(StringContext(io), x) write(io, STRING_DELIM) end """ show_null(io::IO) Print the string `null` to the given IO object `io`. """ show_null(io::IO) = Base.print(io, "null") """ show_element(io::StructuralContext, s, x) Print object `x` as an element of a JSON array to context `io` using rules defined by serialization `s`. """ function show_element(io::JSONContext, s, x) delimit(io) indent(io) show_json(io, s, x) end """ show_key(io::StructuralContext, k) Print string `k` as the key of a JSON key-value pair to context `io`. """ function show_key(io::JSONContext, k) delimit(io) indent(io) show_string(io, k) separate(io) end """ show_pair(io::StructuralContext, s, k, v) Print the key-value pair defined by `k => v` as JSON to context `io`, using rules defined by serialization `s`. """ function show_pair(io::JSONContext, s, k, v) show_key(io, k) show_json(io, s, v) end show_pair(io::JSONContext, s, kv) = show_pair(io, s, first(kv), last(kv)) # Default serialization rules for CommonSerialization (CS) function show_json(io::SC, s::CS, x::IsPrintedAsString) # We need this check to allow `lower(x::Enum)` overrides to work if needed; # it should be optimized out if `lower` is a no-op lx = lower(x) if x === lx show_string(io, x) else show_json(io, s, lx) end end function show_json(io::SC, s::CS, x::Union{Integer, AbstractFloat}) if isfinite(x) Base.print(io, x) else show_null(io) end end show_json(io::SC, ::CS, ::Nothing) = show_null(io) show_json(io::SC, ::CS, ::Missing) = show_null(io) recursive_cycle_check(f, io, s, id) = f() function recursive_cycle_check(f, io::RecursiveCheckContext, s, id) if id in io.objectids show_json(io, s, io.recursive_cycle_token) else push!(io.objectids, id) f() delete!(io.objectids, id) end end function show_json(io::SC, s::CS, x::Union{AbstractDict, NamedTuple}) recursive_cycle_check(io, s, objectid(x)) do begin_object(io) for kv in pairs(x) show_pair(io, s, kv) end end_object(io) end end function show_json(io::SC, s::CS, kv::Pair) begin_object(io) show_pair(io, s, kv) end_object(io) end function show_json(io::SC, s::CS, x::CompositeTypeWrapper) recursive_cycle_check(io, s, objectid(x.wrapped)) do begin_object(io) for fn in x.fns show_pair(io, s, fn, getproperty(x.wrapped, fn)) end end_object(io) end end function show_json(io::SC, s::CS, x::Union{AbstractVector, Tuple}) recursive_cycle_check(io, s, objectid(x)) do begin_array(io) for elt in x show_element(io, s, elt) end end_array(io) end end """ Serialize a multidimensional array to JSON in column-major format. That is, `json([1 2 3; 4 5 6]) == "[[1,4],[2,5],[3,6]]"`. """ function show_json(io::SC, s::CS, A::AbstractArray{<:Any,n}) where n begin_array(io) newdims = ntuple(_ -> :, n - 1) for j in axes(A, n) show_element(io, s, view(A, newdims..., j)) end end_array(io) end # special case for 0-dimensional arrays show_json(io::SC, s::CS, A::AbstractArray{<:Any,0}) = show_json(io, s, A[]) show_json(io::SC, s::CS, a) = show_json(io, s, lower(a)) # Fallback show_json for non-SC types """ Serialize Julia object `obj` to IO `io` using the behaviour described by `s`. If `indent` is provided, then the JSON will be pretty-printed; otherwise it will be printed on one line. If pretty-printing is enabled, then a trailing newline will be printed; otherwise there will be no trailing newline. """ function show_json(io::IO, s::Serialization, obj; indent=nothing) ctx = indent === nothing ? CompactContext(io) : PrettyContext(io, indent) show_json(ctx, s, obj) if indent !== nothing println(io) end end """ JSONText(s::AbstractString) `JSONText` is a wrapper around a Julia string representing JSON-formatted text, which is inserted *as-is* in the JSON output of `JSON.print` and `JSON.json` for compact output, and is otherwise re-parsed for pretty-printed output. `s` *must* contain valid JSON text. Otherwise compact output will contain the malformed `s` and other serialization output will throw a parsing exception. """ struct JSONText s::String end show_json(io::CompactContext, s::CS, json::JSONText) = write(io, json.s) # other contexts for JSONText are handled by lower(json) = parse(json.s) print(io::IO, obj, indent) = show_json(io, StandardSerialization(), obj; indent=indent) print(io::IO, obj) = show_json(io, StandardSerialization(), obj) print(a, indent) = print(stdout, a, indent) print(a) = print(stdout, a) """ json(a) json(a, indent::Int) Creates a JSON string from a Julia object or value. Arguments: • a: the Julia object or value to encode • indent (optional number): if provided, pretty-print array and object substructures by indenting with the provided number of spaces """ json(a) = sprint(print, a) json(a, indent) = sprint(print, a, indent) end 4