Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding JSON macro strings #113

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ JSON.json(j)
# "{\"an_array\":[\"string\",9],\"a_number\":5.0}"
```

### Macro Strings

`JSON` and `J` can be used to embed JSON data directly into source code:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't just json""" be more Julian? Either way, 👎 to two ways to do same thing

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation here needs to be updated to reflect the code changes.


```julia
basic_dict = JSON"""
{
"a_number" : 5.0,
"an_array" : ["string", 9]
}
"""
```


```julia
basic_dict = J"{'a_number' : 5.0, 'an_array' : ['string', 9]}"
```

Note that the shorter `@J_str` relies on the `single_quote` parsing behavior.

## Documentation

```julia
Expand All @@ -50,9 +70,9 @@ json(a::Any)
Returns a compact JSON representation as a String.

```julia
JSON.parse(s::String; ordered=false)
JSON.parse(io::IO; ordered=false)
JSON.parsefile(filename::String; ordered=false, use_mmap=true)
JSON.parse(s::String; ordered=false, single_quote=false)
JSON.parse(io::IO; ordered=false, single_quote=false)
JSON.parsefile(filename::String; ordered=false, single_quote=false, use_mmap=true)
```

Parses a JSON String or IO stream into a nested Array or Dict.
Expand All @@ -61,4 +81,6 @@ If `ordered=true` is specified, JSON objects are parsed into
`OrderedDicts`, which maintains the insertion order of the items in
the object. (*)

Setting `single_quote` enables parsing on non-standard usage of single quote `'\''` for string values.

(*) Requires the `DataStructures.jl` package to be installed.
12 changes: 8 additions & 4 deletions src/JSON.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module JSON

using Compat

export json # returns a compact (or indented) JSON representation as a String
export json, @J_str, @JSON_mstr, @JSON_ORDERED_mstr # returns a compact (or indented) JSON representation as a String

include("Parser.jl")

Expand All @@ -17,8 +17,8 @@ type State{I}
indentlen::Int
prefix::AbstractString
otype::Array{Bool, 1}
State(indentstep::Int) = new(indentstep,
0,
State(indentstep::Int) = new(indentstep,
0,
"",
Bool[])
end
Expand Down Expand Up @@ -282,5 +282,9 @@ function parsefile(filename::AbstractString; ordered::Bool=false, use_mmap=true)
end
end

end # module
# Macros
macro JSON_mstr(arg::AbstractString) parse(arg) end
macro JSON_ORDERED_mstr(arg::AbstractString) parse(arg, ordered=true) end
macro J_str(arg::AbstractString) parse(arg, single_quote=true) end # convenience intented for short single quoted json data

end # module
44 changes: 24 additions & 20 deletions src/Parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ type ParserState{T<:AbstractString}
s::Int
e::Int
end

ParserState(str::AbstractString,s::Int,e::Int) = ParserState(str, s, e)


charat{T<:AbstractString}(ps::ParserState{T}) = ps.str[ps.s]
incr(ps::ParserState) = (ps.s += 1)
hasmore(ps::ParserState) = (ps.s < ps.e)
Expand Down Expand Up @@ -77,13 +79,13 @@ end

# PARSING

function parse_array{T<:AbstractString}(ps::ParserState{T}, ordered::Bool)
function parse_array{T<:AbstractString}(ps::ParserState{T}, ordered::Bool, quote_char::Char)
incr(ps) # Skip over the '['
_array = TYPES[]
chomp_space(ps)
charat(ps)==']' && (incr(ps); return _array) # Check for empty array
while true # Extract values from array
v = parse_value(ps, ordered) # Extract value
v = parse_value(ps, ordered, quote_char) # Extract value
push!(_array, v)
# Eat up trailing whitespace
chomp_space(ps)
Expand All @@ -101,23 +103,23 @@ function parse_array{T<:AbstractString}(ps::ParserState{T}, ordered::Bool)
return _array
end

function parse_object{T<:AbstractString}(ps::ParserState{T}, ordered::Bool)
function parse_object{T<:AbstractString}(ps::ParserState{T}, ordered::Bool, quote_char::Char)
if ordered
parse_object(ps, ordered, OrderedDict{KEY_TYPES,TYPES}())
parse_object(ps, ordered, quote_char, OrderedDict{KEY_TYPES,TYPES}())
else
parse_object(ps, ordered, Dict{KEY_TYPES,TYPES}())
parse_object(ps, ordered, quote_char, Dict{KEY_TYPES,TYPES}())
end
end

function parse_object{T<:AbstractString}(ps::ParserState{T}, ordered::Bool, obj)
function parse_object{T<:AbstractString}(ps::ParserState{T}, ordered::Bool, quote_char::Char, obj)
incr(ps) # Skip over opening '{'
chomp_space(ps)
charat(ps)=='}' && (incr(ps); return obj) # Check for empty object
while true
chomp_space(ps)
_key = parse_string(ps) # Key
_key = parse_string(ps, quote_char) # Key
skip_separator(ps)
_value = parse_value(ps, ordered) # Value
_value = parse_value(ps, ordered, quote_char) # Value
obj[_key] = _value # Building object
chomp_space(ps)
c = charat(ps) # Find the next pair or end of object
Expand All @@ -139,13 +141,13 @@ utf16_get_supplementary(lead::Uint16, trail::Uint16) = @compat(Char(@compat(UInt

# TODO: Try to find ways to improve the performance of this (currently one
# of the slowest parsing methods).
function parse_string{T<:AbstractString}(ps::ParserState{T})
function parse_string{T<:AbstractString}(ps::ParserState{T}, quote_char::Char)
str = ps.str
s = ps.s
e = ps.e

str[s]=='"' || _error("Missing opening string char", ps)
s = nextind(str, s) # Skip over opening '"'
str[s] == quote_char || _error("Missing opening string char", ps)
s = nextind(str, s) # Skip over opening quote_char '"'
b = IOBuffer()
found_end = false
while s <= e
Expand Down Expand Up @@ -180,9 +182,10 @@ function parse_string{T<:AbstractString}(ps::ParserState{T})
elseif c == 'n' write(b, '\n')
elseif c == 'r' write(b, '\r')
elseif c == 't' write(b, '\t')
elseif c == '\'' && quote_char == '\'' write(b, '\'') # not part of standard
else _error("Unrecognized escaped character: " * string(c), ps)
end
elseif c == '"'
elseif c == quote_char
found_end = true
s = nextind(str, s)
break
Expand Down Expand Up @@ -213,19 +216,19 @@ function parse_simple{T<:AbstractString}(ps::ParserState{T})
ret
end

function parse_value{T<:AbstractString}(ps::ParserState{T}, ordered::Bool)
function parse_value{T<:AbstractString}(ps::ParserState{T}, ordered::Bool, quote_char::Char)
chomp_space(ps)
(ps.s > ps.e) && return nothing # Nothing left

ch = charat(ps)
if ch == '"'
ret = parse_string(ps)
if ch == quote_char
ret = parse_string(ps, quote_char)
elseif ch == '{'
ret = parse_object(ps, ordered)
ret = parse_object(ps, ordered, quote_char)
elseif (ch >= '0' && ch <= '9') || ch=='-' || ch=='+'
ret = parse_number(ps)
elseif ch == '['
ret = parse_array(ps, ordered)
ret = parse_array(ps, ordered, quote_char)
elseif ch == 'f' || ch == 't' || ch == 'n'
ret = parse_simple(ps)
else
Expand Down Expand Up @@ -315,12 +318,13 @@ function parse_number{T<:AbstractString}(ps::ParserState{T})
end
end

function parse(str::AbstractString; ordered::Bool=false)
function parse(str::AbstractString; ordered::Bool=false, single_quote::Bool=false)
pos::Int = 1
len::Int = endof(str)
quote_char::Char = single_quote ? '\'' : '\"'
len < 1 && return
ordered && !_HAVE_DATASTRUCTURES && error("DataStructures package required for ordered parsing: try `Pkg.add(\"DataStructures\")`")
parse_value(ParserState(str, pos, len), ordered)
ordered && !_HAVE_DATASTRUCTURES && error("DataStructures package required for ordered parsing: try `Pkg.add(\"DataStructures\")`")
parse_value(ParserState(str, pos, len), ordered, quote_char)
end

end #module Parser
18 changes: 18 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,21 @@ let iob = IOBuffer()
JSON.print(iob, t109(1))
@test get(JSON.parse(takebuf_string(iob)), "i", 0) == 1
end

# Tests for Macro strings

let mstr_test = @compat Dict("a" => 1)
@test mstr_test == JSON"""{"a":1}"""
@test mstr_test == J"{'a':1}"
@test JSON_ORDERED"""{"x": 3}""" == DataStructures.OrderedDict{String,Any}([("x",3)])

@test (@compat Dict("a_number" => 5, "an_array" => ["string"; 9]) ) == J"{'a_number' : 5, 'an_array' : ['string', 9] }"

@test JSON"""
{
"a_number" : 5.0,
"an_array" : ["string", 9]
}
""" == (@compat Dict("a_number" => 5, "an_array" => ["string"; 9]) )

end