Repository: vaidik/commentjson

Issue:
Feature request: Ignore trailing commas
A common workflow of mine is to comment/uncomment items in a JSON file. This fails if I comment out the last item in a list/dictionary because the new last item now has a trailing comma. Is there any appetite for allowing trailing commas? Would a PR that included this functionality get accepted?

Discussion:

Patch:
diff --git a/commentjson/commentjson.py b/commentjson/commentjson.py
index 4702057..998d383 100755
--- a/commentjson/commentjson.py
+++ b/commentjson/commentjson.py
@@ -26,7 +26,9 @@ except ImportError:
 import lark
 
 from lark import Lark
+from lark.lexer import Token
 from lark.reconstruct import Reconstructor
+from lark.tree import Tree
 
 
 parser = Lark('''
@@ -38,12 +40,13 @@ parser = Lark('''
           | "true"             -> true
           | "false"            -> false
           | "null"             -> null
-    array  : "[" [value ("," value)*] "]"
-    object : "{" [pair ("," pair)*] "}"
+    array  : "[" [value ("," value)*] TRAILING_COMMA? "]"
+    object : "{" [pair ("," pair)*]  TRAILING_COMMA? "}"
     pair   : string ":" value
     string : ESCAPED_STRING
 
     COMMENT: /(#|\\/\\/)[^\\n]*/
+    TRAILING_COMMA: ","
 
     %import common.ESCAPED_STRING
     %import common.SIGNED_NUMBER
@@ -151,6 +154,15 @@ class JSONLibraryException(BaseException):
     library = 'json'
 
 
+def _remove_trailing_commas(tree):
+    if isinstance(tree, Tree):
+        tree.children = [
+            _remove_trailing_commas(ch) for ch in tree.children
+                if not (isinstance(ch, Token) and ch.type == 'TRAILING_COMMA')
+        ]
+    return tree
+
+
 def loads(text, *args, **kwargs):
     ''' Deserialize `text` (a `str` or `unicode` instance containing a JSON
     document with Python or JavaScript like comments) to a Python object.
@@ -165,7 +177,7 @@ def loads(text, *args, **kwargs):
         text = text.decode(detect_encoding(text), 'surrogatepass')
 
     try:
-        parsed = parser.parse(text)
+        parsed = _remove_trailing_commas(parser.parse(text))
         final_text = serializer.reconstruct(parsed)
     except lark.exceptions.UnexpectedCharacters:
         raise ValueError('Unable to parse text', text)


Test patch:
diff --git a/commentjson/tests/test_commentjson.py b/commentjson/tests/test_commentjson.py
index feca95b..accb45b 100755
--- a/commentjson/tests/test_commentjson.py
+++ b/commentjson/tests/test_commentjson.py
@@ -20,7 +20,8 @@ class TestCommentJson(unittest.TestCase):
                       'string_with_inline_comment',
                       'inline_has_special_characters',
                       'array_with_hash',
-                      'inline_last_quote')
+                      'inline_last_quote',
+                      'trailing_comma')
 
         for file_ in self.files:
             fpath = os.path.join(self.path, file_)
diff --git a/commentjson/tests/trailing_comma-commented.json b/commentjson/tests/trailing_comma-commented.json
new file mode 100755
index 0000000..1e862da
--- /dev/null
+++ b/commentjson/tests/trailing_comma-commented.json
@@ -0,0 +1,15 @@
+{
+    "a": 1,
+    "c": {
+        # d is cool
+        "d": "4.3",
+        // e is cool
+        "e": {
+            "1": "1 # testing", # test comment
+            "2": "2", # some other stupid comment
+            "2.2": "2", // some other stupid comment
+            "3": "3",
+        },
+    },
+    "b": 2, # b inline comment
+}
diff --git a/commentjson/tests/trailing_comma-uncommented.json b/commentjson/tests/trailing_comma-uncommented.json
new file mode 100755
index 0000000..21b512b
--- /dev/null
+++ b/commentjson/tests/trailing_comma-uncommented.json
@@ -0,0 +1,13 @@
+{
+    "a": 1,
+    "c": {
+        "d": "4.3",
+        "e": {
+            "1": "1 # testing",
+            "2": "2",
+            "2.2": "2",
+            "3": "3"
+        }
+    },
+    "b": 2
+}