I used @iago-lito's answer in a script I wrote a few years ago. Yesterday I spent some time improving on it. The vim dictionary is very similar to a JSON object, but:
- when I open the file and
set filetype=json
, the linter complains about the single quotes around the strings, and
- the JSON formatter splits the text into multiple lines, and indents them to make a pretty file. As a result, reading only the 0'th line of text doesn't give a complete dictionary object.
Here are my modifications to fix both issues.
function! SaveVariable(var, file)
" Change all single quotes to double quotes.
" {'x':'O''Toole','y':['A',2,'d']} -> {"x":"O""Toole","y":["A",2,"d"]}
let serialized = substitute(string(a:var),"'",'"','g')
" Change all escaped double quotes back to apostrophes.
" {"x":"O""Toole","y":["A",2,"d"]} -> {"x":"O'Toole","y":["A",2,"d"]}
let serialized = substitute(serialized,'""', "'",'g')
call writefile([serialized], a:file)
endfunction
function! ReadVariable(file)
execute 'let result = [' . join(readfile(a:file),'') . ']'
return result[0]
endfunction
This seems to work well for all kinds of data. I tested it with an object, a list, and number and string scalar values.
STRANGER, DANGER!
Here is a word of warning that goes along with this and any other dynamically-generated code. Both @iago-lito's and my solution are vulnerable to code injection, and if you are reading files that are out of your control, bad things can happen to your machine. For example, if someone sneaks this into the file:
42|call system('rmdir /s /q c:\')|call system('rm -rf /')
calling @iago-lito's ReadVariable()
will return 42, but your computer will be toast, whether it's a Windows, Mac, or Linux machine. My version also fails, albeit with a more complex version of the statement:
42]|call system('rmdir /s /q c:\')|call system('rm -rf /')|let x=[
A proper solution would be to parse the text, looking for the end of the actual data, and dumping everything after it. This means you lose the simplicity of this approach. Vim and Neovim have come a long way in recent years. lua and python are, from what I've read, easier than ever to integrate into vimscript. I wouldn't be surprised if either of those languages has a built-in answer to this question.
string()
formatted variables to the same file, thensource
the entire file (all variables) in one go. Perhaps add an "append" flag? But you know your requirements best - I think you prefer to collect everything in a single, to-be-serialized, dictionary. – Mirnamirror