Repository: pallets-eco/wtforms

Issue:
Form field errors not JSON serializable with default Flask JSONProvider
The decision in 3.0 to use None as a dictionary key for holding form level errors #595 has the unfortunate side-effect of not allowing the JSON serializer to sort keys - which is the default set up by Flask.

One gets the error: `TypeError: '<' not supported between instances of 'NoneType' and 'str'`

Not sure of a good solution since it has been this way for a couple years now - a simple but not great solution would be to offer a configuration option to change the key.

I also realize that wtforms isn't explicitly tied to Flask - though as a low level library it would be nice if it didn't force the behavior of applications and higher level frameworks.


### Environment

* Python version: 3.9
* wtforms version: 3.1.2

Hints from issue comments:
@davidism: I don't mind just changing it to `""` or `"__form__"` in a feature release.@jwag956: Thanks - a comment in #595 suggests '__all__' - same as django - I don't really care - though using anything that could be confused as core python I think would just cause confusion (also - it isn't all errors!).   '__form__' seems pretty safe.@davidism: I use the empty string `""` in another API library I maintain, which isn't otherwise valid as a form field name.

Solution patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index b05d6e0..5f12719 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,14 @@ Unreleased
   can validate multiple valued fields like :class:`~fields.SelectMultipleField`
   :pr:`538` :pr:`807`
 
+- Breaking change: The key for form errors moved from :data:`None` to
+  empty string `""`. :issue:`829` :pr:`858`
+
+.. note::
+   If you need to keep the old behavior you can set the ``_form_error_key``
+   parameter of you form to :data:`None`.
+
+
 Version 3.1.2
 -------------
 
diff --git a/src/wtforms/form.py b/src/wtforms/form.py
index b462936..d0a77ba 100644
--- a/src/wtforms/form.py
+++ b/src/wtforms/form.py
@@ -30,6 +30,7 @@ class BaseForm:
             prefix += "-"
 
         self.meta = meta
+        self._form_error_key = ""
         self._prefix = prefix
         self._fields = OrderedDict()
 
@@ -155,7 +156,7 @@ class BaseForm:
     def errors(self):
         errors = {name: f.errors for name, f in self._fields.items() if f.errors}
         if self.form_errors:
-            errors[None] = self.form_errors
+            errors[self._form_error_key] = self.form_errors
         return errors
 
 


Test patch:
diff --git a/tests/test_form.py b/tests/test_form.py
index e02e3e6..d64cc21 100644
--- a/tests/test_form.py
+++ b/tests/test_form.py
@@ -204,7 +204,7 @@ def test_form_level_errors():
     f = F(a=0, b=1)
     assert not f.validate()
     assert ["a + b should be even"] == f.form_errors
-    assert ["a + b should be even"] == f.errors[None]
+    assert ["a + b should be even"] == f.errors[""]
 
 
 def test_field_adding_disabled():