Json/bson dsl

Hi,

I’ve been playing a bit with Mongoc which is a thin layer on top of the MongoDB C library. MongoDB is leaning heavily into JSON, which makes for non-intuitive code (from the Mongoc docs):

create_indexes_cmd = Mongoc.BSON(
        "createIndexes" => collection_name,
        "indexes" => [ Mongoc.BSON("key" => Mongoc.BSON("group" => 1), "name" => "group_index") ]
    )

Julia is an amazing language for making your own DSL. I’m wondering why no one implemented a JSON DSL? This seems quite easy.

It took me ~ 30 minutes to hack the following (I’ve never written a Julia macro before):

parse_expr(e) = e

function parse_expr(e::Expr)
    if e.head == :braces
        d = Dict()
        for f in e.args
            f isa Expr || error("not a valid JSON")
            if f.head == :call
                f.args[1] == :(:) || error("not a valid JSON")
                f2 = f.args[2]
                typeof(f2) ∈ (Symbol, String) || error("not a valid JSON")
                v = parse_expr(f.args[3])
                d[f2] = v
            end
        end
        return d
    elseif e.head == :vect
        v = similar(e.args)
        for i in eachindex(e.args)
            a = 
            v[i] = parse_expr(e.args[i])
        end
        return v
    end
end

macro json(e)
    return parse_expr(e)
end

@json { a: [{"b": "c"}, 5, 2.0] }

# returns:
# Dict{Any, Any} with 1 entry:
#  :a => Any[Dict{Any, Any}("b"=>"c"), 5, 2.0]

The Mongoc example from above then becomes:

j = @json {"createIndexes": "collname",
        "indexes" : [ { "key": {"group": 1}, "name" : "group_index" } ] }

@show j
#j = Dict{Any, Any}("createIndexes" => "collname", "indexes" => Any[Dict{Any, Any}("key" => Dict{Any, Any}("group" => 1), "name" => "group_index")]) 
1 Like

A better version, now supporting local variables:

json(n) = n
json(n::Symbol) = esc(n)

function json(n::Expr)
    if n.head == :vect
        return Expr(:vect, map(json, n.args)...)
    elseif n.head == :braces
        kv = []
        for f in n.args
            f isa Expr || error("not a valid JSON")
            f.args[1] == :(:) || error("not a valid JSON")
            k = f.args[2]

            typeof(k) ∈ (Symbol, String) || error("not a valid JSON")
            k = string(k)
            v = json(f.args[3])
            push!(kv, :($k=>$v))
        end
        return :(Dict{String, Any}($(kv...)))
    else
        return esc(n)
    end
end

macro json(n)
    return json(n)
end

function aa()
    i = 2
    j = 3
    return @json({a : {b:i}, "c": [1,2,3.0], "d": i+j})
end

aa()
# Dict{String, Any} with 3 entries:
#  "c" => [1.0, 2.0, 3.0]
#  "a" => Dict{String, Any}("b"=>2)
#  "d" => 5
1 Like