Add std/json quirk_allow_final_comma
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index fe922b0..b615ecc 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -21320,9 +21320,17 @@
               (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
               (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
           if (0 == (v_expect & 256)) {
-            v_expect = 4098;
+            if (self->private_impl.f_quirk_enabled_allow_final_comma) {
+              v_expect = 4162;
+            } else {
+              v_expect = 4098;
+            }
           } else {
-            v_expect = 7858;
+            if (self->private_impl.f_quirk_enabled_allow_final_comma) {
+              v_expect = 8114;
+            } else {
+              v_expect = 7858;
+            }
           }
           goto label__outer__continue;
         } else if (v_class == 3) {
diff --git a/std/json/decode_json.wuffs b/std/json/decode_json.wuffs
index 176fd09..566811d 100644
--- a/std/json/decode_json.wuffs
+++ b/std/json/decode_json.wuffs
@@ -832,9 +832,17 @@
 				// What's valid after a comma depends on whether or not we're
 				// in an array or an object.
 				if 0 == (expect & 0x0100) {  // 0x0100 is (1 << CLASS_CLOSE_SQUARE_BRACKET).
-					expect = 0x1002  // 0x1002 is EXPECT_STRING.
+					if this.quirk_enabled_allow_final_comma {
+						expect = 0x1042  // 0x1042 is EXPECT_STRING | EXPECT_CLOSE_CURLY_BRACE.
+					} else {
+						expect = 0x1002  // 0x1002 is EXPECT_STRING.
+					}
 				} else {
-					expect = 0x1EB2  // 0x0EB2 is EXPECT_VALUE.
+					if this.quirk_enabled_allow_final_comma {
+						expect = 0x1FB2  // 0x0FB2 is EXPECT_VALUE | EXPECT_CLOSE_SQUARE_BRACKET.
+					} else {
+						expect = 0x1EB2  // 0x0EB2 is EXPECT_VALUE.
+					}
 				}
 				continue.outer
 
diff --git a/test/c/std/json.c b/test/c/std/json.c
index e345344..f1bfc1a 100644
--- a/test/c/std/json.c
+++ b/test/c/std/json.c
@@ -1330,6 +1330,74 @@
 }
 
 const char*  //
+test_wuffs_json_decode_quirk_allow_final_comma() {
+  CHECK_FOCUS(__func__);
+
+  struct {
+    // want has 2 bytes, one for each possible q:
+    //  - q&1 sets WUFFS_JSON__QUIRK_ALLOW_FINAL_COMMA.
+    // An 'X', '+' or '-' means that decoding should succeed (and consume the
+    // entire input), succeed (without consuming the entire input) or fail.
+    const char* want;
+    const char* str;
+  } test_cases[] = {
+      {.want = "-X", .str = "[0,]"},
+      {.want = "-X", .str = "[[], {},{\"k\":\"v\",\n}\n,\n]"},
+      {.want = "--", .str = "[,]"},
+      {.want = "--", .str = "{,}"},
+  };
+
+  int tc;
+  for (tc = 0; tc < WUFFS_TESTLIB_ARRAY_SIZE(test_cases); tc++) {
+    int q;
+    for (q = 0; q < 2; q++) {
+      wuffs_json__decoder dec;
+      CHECK_STATUS("initialize", wuffs_json__decoder__initialize(
+                                     &dec, sizeof dec, WUFFS_VERSION,
+                                     WUFFS_INITIALIZE__DEFAULT_OPTIONS));
+      wuffs_json__decoder__set_quirk_enabled(
+          &dec, WUFFS_JSON__QUIRK_ALLOW_FINAL_COMMA, q & 1);
+
+      wuffs_base__token_buffer tok =
+          wuffs_base__make_token_buffer_writer(global_have_token_slice);
+      wuffs_base__io_buffer src = wuffs_base__make_io_buffer_reader(
+          wuffs_base__make_slice_u8((void*)(test_cases[tc].str),
+                                    strlen(test_cases[tc].str)),
+          true);
+      const char* have =
+          wuffs_json__decoder__decode_tokens(&dec, &tok, &src).repr;
+      const char* want =
+          (test_cases[tc].want[q] != '-') ? NULL : wuffs_json__error__bad_input;
+      if (have != want) {
+        RETURN_FAIL("tc=%d, q=%d: decode_tokens: have \"%s\", want \"%s\"", tc,
+                    q, have, want);
+      }
+
+      size_t total_length = 0;
+      while (tok.meta.ri < tok.meta.wi) {
+        total_length += wuffs_base__token__length(&tok.data.ptr[tok.meta.ri++]);
+      }
+      if (total_length != src.meta.ri) {
+        RETURN_FAIL("tc=%d, q=%d: total_length: have %zu, want %zu", tc, q,
+                    total_length, src.meta.ri);
+      }
+      if (test_cases[tc].want[q] == 'X') {
+        if (total_length != src.data.len) {
+          RETURN_FAIL("tc=%d, q=%d: total_length: have %zu, want %zu", tc, q,
+                      total_length, src.data.len);
+        }
+      } else if (test_cases[tc].want[q] == '+') {
+        if (total_length >= src.data.len) {
+          RETURN_FAIL("tc=%d, q=%d: total_length: have %zu, want < %zu", tc, q,
+                      total_length, src.data.len);
+        }
+      }
+    }
+  }
+  return NULL;
+}
+
+const char*  //
 test_wuffs_json_decode_quirk_allow_comment_etc() {
   CHECK_FOCUS(__func__);
 
@@ -2046,6 +2114,7 @@
     test_wuffs_json_decode_quirk_allow_backslash_etc,
     test_wuffs_json_decode_quirk_allow_backslash_x,
     test_wuffs_json_decode_quirk_allow_comment_etc,
+    test_wuffs_json_decode_quirk_allow_final_comma,
     test_wuffs_json_decode_quirk_allow_leading_etc,
     test_wuffs_json_decode_quirk_allow_trailing_etc,
     test_wuffs_json_decode_quirk_replace_invalid_utf_8,