Here is a way to do this with literate programming in org.
#+BEGIN_SRC json :tangle config.json
{"key1": "value",
#+END_SRC
For key2 I prefer 0 instead of 1
#+BEGIN_SRC json :tangle config.json
"key2": 0}
#+END_SRC
Here we automate tangling, and then show it is valid json.
#+BEGIN_SRC emacs-lisp :var tangle=(org-babel-tangle)
(json-read-file "config.json")
#+END_SRC
#+RESULTS:
: ((key1 . value) (key2 . 0))
Is there a smarter way? I guess it depends on where you want your comments. You could use elisp to do it like this.
#+BEGIN_SRC emacs-lisp
(let ((data '((key-1 . value)
;; I like another-value for key-2
(key-2 . another-value))))
(with-temp-file "config-2.json"
(insert (json-encode data))))
(json-read-file "config-2.json")
#+END_SRC
You could also use noweb, and define your own execute function like this.
#+BEGIN_SRC emacs-lisp
(defun org-babel-execute:config-json (body params)
(message "%S" params)
(let* ((vars (seq-filter (lambda (x) (eq (car x) :var)) params))
(config (cl-loop for var in vars
if (eq (car (cdr var)) 'config)
return (cddr var))))
(with-temp-file config (insert (org-babel-expand-body:generic body params)))))
#+END_SRC
#+RESULTS:
: org-babel-execute:config-json
#+name: key-value-1
#+BEGIN_SRC config-json
"key1": 0
#+END_SRC
#+name: key-value-2
#+BEGIN_SRC config-json
"key2": 1
#+END_SRC
When you "execute" this block it will write out config3.json.
#+name: config
#+BEGIN_SRC config-json :noweb yes :var config="config3.json"
{<<key-value-1>>, <<key-value-2>>}
#+END_SRC
#+RESULTS: config
#+BEGIN_SRC sh :results raw
cat config3.json
#+END_SRC
#+RESULTS:
{"key1": 0, "key2": 1}
json-mode
allows comments.