Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Export a Vimscript Dictionary as JSON

Tags:

json

vim

I have a found a plugin that enables Vim to parse JSON. I need to export VimScript dictionaries as JSON. Currently I am just using:

let str = string(dict)
substitute(str, "'", '"', 'g')

This is working but is bound to break when I run into dictionaries with embedded quotes. What is a better way?

like image 432
Sean Mackesey Avatar asked Apr 26 '14 20:04

Sean Mackesey


3 Answers

I am not sure whether this should be a separate answer, an edit to @Kent's answer, or a comment on @Kent's answer. Here is a version of @Kent's function with a few simplifications:

function! ToJson(input)
    let json = ''
    if type(a:input) == type({})
        let json .= "{"
        let di =  0
        for key in keys(a:input)
            let di += 1
            let json .= '"'.escape(key, '"').'":'
            let json .= ToJson(a:input[key])
            let json .= di<len(a:input)? "," : ""
        endfor
        let json .= "}"
    elseif type(a:input) == type([])
        let json .= "["
        let li = 0
        for e in a:input
            let li += 1
            let json .= ToJson(e)
            if li<len(a:input)
                let json .= ","
            endif
        endfor
        let json .=  "]"

    else
        let json .= '"'.escape(a:input, '"').'"'
    endif
    return json
endfunction

Instead of echoing the result, this returns it as a String. Also, I use escape() instead of substitute(). Finally, I think (Check this!) that it is safe to use escape() as I did without first checking that the argument is a String.

Here is a shorter version, using map(). I do not know whether depth of recursion matters more for this version or the others, but if your input is big enough for that to matter, it probably goes faster if we let map() handle the recursion.

function! ToJson(input)
    let json = ''
    if type(a:input) == type({})
        let parts = copy(a:input)
        call map(parts, '"\"" . escape(v:key, "\"") . "\":" . ToJson(v:val)')
        let json .= "{" . join(values(parts), ",") . "}"
    elseif type(a:input) == type([])
        let parts = map(copy(a:input), 'ToJson(v:val)')
        let json .= "[" . join(parts, ",") . "]"
    else
        let json .= '"'.escape(a:input, '"').'"'
    endif
    return json
endfunction

Using either version, I get the same result as @Kent's function, except for whitespace. I have not tested it with anything more complex than @Kent's d1. It might be safer to use deepcopy() than copy().

like image 79
benjifisher Avatar answered Oct 17 '22 19:10

benjifisher


Since Vim 7.4.1304 (so definitely available with Vim 8.0), these functions are built into Vim as json_encode() and json_decode().

For backwards compatibility with even older Vim versions, the WebAPI.vim plugin has a JSON parser and encoder implemented in pure Vimscript:

:let jsonString = webapi#json#encode({...})
like image 3
Ingo Karkat Avatar answered Oct 17 '22 19:10

Ingo Karkat


I wrote a little function to echo the json string, with " double quote escaped. not sure if it works for your needs:

function! ToJson(input)
    if type(a:input) == type({})
        echo "{"
        let di =  0
        for key in keys(a:input)
            let di += 1
            if type(key) == type('')
                echo '"'.substitute(key, '"', '\\"','g').'":'
            else
                echo '"'.key.'":'
            endif
            call ToJson(a:input[key])
            echo di<len(a:input)? "," : ""
        endfor
        echo "}"
    elseif type(a:input) == type([])
        echo "["
        let li = 0
        for e in a:input
            let li += 1
            call ToJson(e)
            if li<len(a:input)
                echo ","
            endif
        endfor
        echo  "]"

    elseif type(a:input) == type('')
        echo '"'.substitute(a:input, '"', '\\"','g').'"'
    else
        echo '"'.a:input.'"'
    endif
endfunction

a dict like:

let d1={'one':'"""', 'two':123, 333:['11',22,'"_"_"'], 'four':"''"}

will be output as:

{
"four":
"''"
,
"one":
"\"\"\""
,
"two":
"123"
,
"333":
[
"11"
,
"22"
,
"\"_\"_\""
]
}

I didn't do much debug/testing. also the format looks not nice, but I guess you don't care about the format since you used string()...

the above output could be formatted into (by an online json formatter):

{
   "four":"''",
   "one":"\"\"\"",
   "two":"123",
   "333":[
      "11",
      "22",
      "\"_\"_\""
   ]
}

hope it helps.

like image 2
Kent Avatar answered Oct 17 '22 18:10

Kent