Skip to content

Commit ab45a20

Browse files
authored
Teach json.dumps() to encode jsnull as null (pyodide#5804)
1 parent 49cce94 commit ab45a20

12 files changed

+138
-24
lines changed

cpython/patches/0001-Public-pymain_run_python.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From cefd199c4434444faad9dab7846369bc5e4462e6 Mon Sep 17 00:00:00 2001
1+
From 6ebddb6b930268d54359eebc5454fd75b28f236c Mon Sep 17 00:00:00 2001
22
From: Hood Chatham <[email protected]>
33
Date: Sun, 17 Jul 2022 14:40:39 +0100
4-
Subject: [PATCH 1/8] Public pymain_run_python
4+
Subject: [PATCH 01/10] Public pymain_run_python
55

66
Discussion here:
77
https://discuss.python.org/t/unstable-api-for-pymain-run-python-run-python-cli-but-dont-finalize-interpreter/44675

cpython/patches/0002-Add-emscripten-platform-support-to-ctypes.util.find_.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From 0aef39a0c0965499f2af0865c73aeb5cf7fd6030 Mon Sep 17 00:00:00 2001
1+
From 725564a58fb173466dc32b931629806719b75e97 Mon Sep 17 00:00:00 2001
22
From: ryanking13 <[email protected]>
33
Date: Fri, 2 Dec 2022 11:36:44 +0000
4-
Subject: [PATCH 2/8] Add emscripten platform support to
4+
Subject: [PATCH 02/10] Add emscripten platform support to
55
ctypes.util.find_library
66

77
---

cpython/patches/0003-Fix-LONG_BIT-constant-to-be-always-32bit.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From 86b3f8718157b48de0f04bed5c795f4e6c2b80d5 Mon Sep 17 00:00:00 2001
1+
From 9c76fbcd5e3654541d5c6303e1ca180425ba158c Mon Sep 17 00:00:00 2001
22
From: ryanking13 <[email protected]>
33
Date: Fri, 12 Jan 2024 00:52:57 +0900
4-
Subject: [PATCH 3/8] Fix LONG_BIT constant to be always 32bit
4+
Subject: [PATCH 03/10] Fix LONG_BIT constant to be always 32bit
55

66
Starting from Emscripten 3.1.50, there is an issue where LONG_BIT is
77
calculated to 64 for some reason. This is very strange because LONG_MAX

cpython/patches/0004-Warn-if-ZoneInfo-is-imported-without-tzdata.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From bc204c2e93cd9daa272f539781626bed9e70962e Mon Sep 17 00:00:00 2001
1+
From 198ad7882105abb53e3b57c1aa44a73ad96c7bd3 Mon Sep 17 00:00:00 2001
22
From: Hood Chatham <[email protected]>
33
Date: Thu, 25 Jul 2024 14:28:57 +0200
4-
Subject: [PATCH 4/8] Warn if ZoneInfo is imported without tzdata
4+
Subject: [PATCH 04/10] Warn if ZoneInfo is imported without tzdata
55

66
---
77
Lib/zoneinfo/_common.py | 6 ++++++

cpython/patches/0005-Add-call-to-JsProxy_GetMethod-to-help-remove-tempora.patch

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
From 85954e82a63e025cf466fb8972ff207115c1f02e Mon Sep 17 00:00:00 2001
1+
From 76360edbea0808455523e42f024894215b483ebd Mon Sep 17 00:00:00 2001
22
From: Hood Chatham <[email protected]>
33
Date: Thu, 25 Jul 2024 14:41:37 +0200
4-
Subject: [PATCH 5/8] Add call to `JsProxy_GetMethod` to help remove temporary
4+
Subject: [PATCH 05/10] Add call to `JsProxy_GetMethod` to help remove
5+
temporary
56

67
`_PyObject_GetMethod` is a special attribute lookup function that won't call the
78
`__get__` descriptor on a method to avoid creating a temporary PyMethodObject.

cpython/patches/0006-Make-from-x-import-aware-of-jsproxy-modules.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From 9e58f06e7502ff57943ef38d60b1f31761f9b7f0 Mon Sep 17 00:00:00 2001
1+
From 01a53bd90d3e11992489989a2db45e41bd9b1947 Mon Sep 17 00:00:00 2001
22
From: Hood Chatham <[email protected]>
33
Date: Sat, 22 Feb 2025 13:18:18 +0100
4-
Subject: [PATCH 6/8] Make `from x import *` aware of jsproxy modules
4+
Subject: [PATCH 06/10] Make `from x import *` aware of jsproxy modules
55

66
---
77
Python/intrinsics.c | 8 ++++++++

cpython/patches/0007-Use-wasm-gc-based-call-adaptor-if-available.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From c1644713f337237d3f2d35f60237c0f123aee9e9 Mon Sep 17 00:00:00 2001
1+
From ebcde519cfa69f0a514555e46436379789f6d1bb Mon Sep 17 00:00:00 2001
22
From: Hood Chatham <[email protected]>
33
Date: Tue, 11 Mar 2025 14:31:31 +0100
4-
Subject: [PATCH 7/8] Use wasm-gc based call adaptor if available
4+
Subject: [PATCH 07/10] Use wasm-gc based call adaptor if available
55

66
Part of the ongoing quest to support JSPI. The JSPI spec removed its dependence on
77
JS type reflection, and now the plan is for runtimes to ship JSPI while keeping

cpython/patches/0008-Fix-Emscripten-call-trampoline-compatibility-with-Em.patch

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From 062c19de9bcf764def1c5b0721184c0397624572 Mon Sep 17 00:00:00 2001
1+
From 62c9c54822dd8e313d1a2ea4ffaba13c6623ed92 Mon Sep 17 00:00:00 2001
22
From: Hood Chatham <[email protected]>
33
Date: Mon, 31 Mar 2025 12:09:50 +0200
4-
Subject: [PATCH 8/8] Fix Emscripten call trampoline compatibility with
4+
Subject: [PATCH 08/10] Fix Emscripten call trampoline compatibility with
55
Emscripten 4.0.3 and 4.0.4
66

77
See upstream issue:
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
From 738e25376b23611b6077c71f5ef55eefa66dd217 Mon Sep 17 00:00:00 2001
1+
From 6f119c924bb218b3cddc9c0683ac2efc56ebf463 Mon Sep 17 00:00:00 2001
22
From: ryanking13 <[email protected]>
3-
Date: Wed, 11 Jun 2025 08:54:37 +0000
4-
Subject: [PATCH 9/9] Fix iPad detection in wasm-gc
3+
Date: Wed, 11 Jun 2025 08:54:37 +0200
4+
Subject: [PATCH 09/10] Fix iPad detection in wasm-gc
55

66
Fixes iPad detection logic in wasm-gc, which was malfunctioning in recent iPadOS + safari.
7-
87
---
98
Python/emscripten_trampoline.c | 9 ++++++++-
109
1 file changed, 8 insertions(+), 1 deletion(-)
1110

1211
diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c
13-
index cc5047d6bd..64315ad5e9 100644
12+
index 1b5380a4031..980a2361b69 100644
1413
--- a/Python/emscripten_trampoline.c
1514
+++ b/Python/emscripten_trampoline.c
16-
@@ -71,7 +71,14 @@ EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), {
17-
// )
15+
@@ -32,7 +32,14 @@ EM_JS(void, _PyEM_InitTrampoline_js, (), {
16+
}
1817

1918
function getPyEMCountArgsPtr() {
2019
- let isIOS = globalThis.navigator && /iPad|iPhone|iPod/.test(navigator.platform);
@@ -30,5 +29,5 @@ index cc5047d6bd..64315ad5e9 100644
3029
return 0;
3130
}
3231
--
33-
2.49.0
32+
2.34.1
3433

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
From 7cf6863176a7b3b62980e25652102532582ae49e Mon Sep 17 00:00:00 2001
2+
From: Hood Chatham <[email protected]>
3+
Date: Wed, 9 Jul 2025 17:00:12 +0200
4+
Subject: [PATCH 10/10] Teach json encoder.py to encode jsnull
5+
6+
---
7+
Lib/json/encoder.py | 9 +++++++++
8+
Modules/_json.c | 8 +++++---
9+
2 files changed, 14 insertions(+), 3 deletions(-)
10+
11+
diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py
12+
index 323332f064e..f0aeb1a0b0f 100644
13+
--- a/Lib/json/encoder.py
14+
+++ b/Lib/json/encoder.py
15+
@@ -33,6 +33,7 @@
16+
del i
17+
18+
INFINITY = float('inf')
19+
+_JSNULL = None
20+
21+
def py_encode_basestring(s):
22+
"""Return a JSON representation of a Python string
23+
@@ -303,6 +304,8 @@ def _iterencode_list(lst, _current_indent_level):
24+
yield buf + _encoder(value)
25+
elif value is None:
26+
yield buf + 'null'
27+
+ elif value is _JSNULL:
28+
+ yield buf + 'null'
29+
elif value is True:
30+
yield buf + 'true'
31+
elif value is False:
32+
@@ -368,6 +371,8 @@ def _iterencode_dict(dct, _current_indent_level):
33+
key = 'false'
34+
elif key is None:
35+
key = 'null'
36+
+ elif key is _JSNULL:
37+
+ key = 'null'
38+
elif isinstance(key, int):
39+
# see comment for int/float in _make_iterencode
40+
key = _intstr(key)
41+
@@ -386,6 +391,8 @@ def _iterencode_dict(dct, _current_indent_level):
42+
yield _encoder(value)
43+
elif value is None:
44+
yield 'null'
45+
+ elif key is _JSNULL:
46+
+ yield 'null'
47+
elif value is True:
48+
yield 'true'
49+
elif value is False:
50+
@@ -416,6 +423,8 @@ def _iterencode(o, _current_indent_level):
51+
yield _encoder(o)
52+
elif o is None:
53+
yield 'null'
54+
+ elif o is _JSNULL:
55+
+ yield 'null'
56+
elif o is True:
57+
yield 'true'
58+
elif o is False:
59+
diff --git a/Modules/_json.c b/Modules/_json.c
60+
index e33ef1f5eea..3152abd27f3 100644
61+
--- a/Modules/_json.c
62+
+++ b/Modules/_json.c
63+
@@ -1302,11 +1302,13 @@ encoder_call(PyEncoderObject *self, PyObject *args, PyObject *kwds)
64+
return result;
65+
}
66+
67+
+extern PyObject* py_jsnull;
68+
+
69+
static PyObject *
70+
_encoded_const(PyObject *obj)
71+
{
72+
/* Return the JSON string representation of None, True, False */
73+
- if (obj == Py_None) {
74+
+ if (obj == Py_None || obj == py_jsnull) {
75+
return &_Py_ID(null);
76+
}
77+
else if (obj == Py_True) {
78+
@@ -1385,7 +1387,7 @@ encoder_listencode_obj(PyEncoderObject *s, _PyUnicodeWriter *writer,
79+
PyObject *newobj;
80+
int rv;
81+
82+
- if (obj == Py_None) {
83+
+ if (obj == Py_None || obj == py_jsnull) {
84+
return _PyUnicodeWriter_WriteASCIIString(writer, "null", 4);
85+
}
86+
else if (obj == Py_True) {
87+
@@ -1490,7 +1492,7 @@ encoder_encode_key_value(PyEncoderObject *s, _PyUnicodeWriter *writer, bool *fir
88+
else if (PyFloat_Check(key)) {
89+
keystr = encoder_encode_float(s, key);
90+
}
91+
- else if (key == Py_True || key == Py_False || key == Py_None) {
92+
+ else if (key == Py_True || key == Py_False || key == Py_None || key == py_jsnull) {
93+
/* This must come before the PyLong_Check because
94+
True and False are also 1 and 0.*/
95+
keystr = _encoded_const(key);
96+
--
97+
2.34.1
98+

0 commit comments

Comments
 (0)