Skip to content

JSON support - #56

Merged
jcrist merged 50 commits into
masterfrom
json2
Jan 31, 2022
Merged

jcrist merged 50 commits into
masterfrom
json2

Conversation

@jcrist

@jcrist jcrist commented Jan 12, 2022 •

Copy link
Copy Markdown
Member

This PR:

  • Moves the msgpack encoder/decoder from the top level namespace to msgspec.msgpack.*, allowing for other encodings to be supported
  • Adds a JSON encoder/decoder in msgspec.json.*. This encoding has parity with the msgspec.msgpack encoding, allowing for easy switching between the two encodings.

The JSON implementation is fairly optimized (there are likely still optimizations todo). It's competitive with orjson for common workloads, and if typed decoding is used it's frequently measurably faster.

There's still a fair bit to do:

  • Tests. Needs way more tests.
  • datetime support
  • [ ] Update documentation (edit: some is done, the rest will be done in a follow-up PR)

Fixes #8, supersedes #46.

Move all msgpack specific things out of the top-level namespace to
`msgspec.msgpack`. This is in preparation for supporting other encodings
like `json`.
Still needs lots of work, but the objects & methods are there.
Also fixup list/set decoding and (slightly) improve error messages.
Pulling character escaping out into a separate function lets clang
optimize this routine further. Clang is ~40% faster than gcc for some
benchmarks here, not sure why.
Implements the Eisel-Lemire algorithm for float parsing, based on the
original implementation, Nigel Tao's blogpost, and the implementation in
Wuffs. A fallback method still is needed for cases where Eisel-Lemire
fails.
Add json asarray, a few minor cleanups.
@jcrist jcrist mentioned this pull request Jan 12, 2022
3 tasks
- Error if trailing characters are present in both JSON and msgpack
decoders.
- Fix a bug in utf-16 codepoint decoding

After these changes, the JSON decoder fully passes the JSONTestSuite
tests at https://github.com/nst/JSONTestSuite.
We now track the path to the current object in the deserializer,
and display the faulty path in the error message.
jcrist added 21 commits January 18, 2022 11:21
Track start, end, and position pointers instead of start + read index.
This was already done in the JSON decoder (where a position pointer was
a bit easier to work with), we know do this in the msgpack decoder as
well.
We were using a mix of `js_`/`json_` and `mp_`/`mpack_` prefixes.
Standardize on the longer prefixes.
- Remove need for `TypeNode_Repr` - we now rely on the builtin reprs
- Add module name to decoder reprs
Datetimes are encoded as RFC 3339 formatted strings.
Previously we attempted to support serializing both naive and aware
datetime objects. Since MsgPack timestamps are by nature "aware", we
interpreted naive datetimes implicitly as representing local time. This
assumption could be a footgun for unaware users, and so is removed. Only
aware datetimes are supported.

This commit also dramatically improves the performance of msgpack
datetime encoding/decoding (which previously relied on calling into
python methods to do the conversion). datetime <-> epoch conversion is
now handled entirely within msgspec itself, resulting in ~10x speedup on
encoding and ~5x speedup on decoding.
Previously for JSON datetimes with a timezone offset, we'd decode the
datetime into the specified timezone. To avoid the cost of creating a
new `datetime.timezone` object every time, we'd cache the most recent
timezone to reuse it if possible. However, the msgpack decoder always
decodes datetimes into UTC, and most users will probably want UTC
anyway. To keep things compatible, we now always decode all datetimes
into UTC for JSON, applying the offset as needed. An added perk is that
this is much faster, as we don't ever hit Python method calls on decode.
These are tests for valid/invalid JSON, not correctness of parsing.
Also fixup a few bugs in the float parsing routines.
Thank goodness for fuzz testing.
Not all types are JSON compatible (e.g. dicts with non-str keys). We
previously attempted to handle this, but it didn't cover all cases. We
now improve this error handling, and add test cases for this behavior.
Add tests for datetimes and structs
Still need to redo the prose docs, but in a later PR
Still need to redo the full docs, but this at least is a start.
@jcrist jcrist changed the title [WIP] - JSON support JSON support Jan 31, 2022
@jcrist
jcrist merged commit a6cceb1 into master Jan 31, 2022
@jcrist
jcrist deleted the json2 branch January 31, 2022 18:46
sobolevn added a commit to Andarius/msgspec that referenced this pull request Oct 12, 2026
`MS_CONSTR_STR_REGEX` slot clear was missing. It was not properly
traversed and freed. Now it is.

It used to fail with memory sanitized with:

```
 ==2637==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x50e000e8e350 in thread T0
    #0 0x7f07a10fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    msgspec#1 0x7f079c42ddf4 in TypeNode_Free src/msgspec/_core.c:3474
    msgspec#2 0x7f079c42dbdf in TypeNode_Free src/msgspec/_core.c:3468
    msgspec#3 0x7f079c55e2eb in JSONDecoder_dealloc src/msgspec/_core.c:17235
    msgspec#4 0x7f07a0999132 in _PyEval_EvalFrameDefault Python/bytecodes.c:1398
    msgspec#5 0x7f07a0a15f73 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:92
    msgspec#6 0x7f07a0a15f73 in method_vectorcall Objects/classobject.c:69
    msgspec#7 0x7f07a098d82f in _PyEval_EvalFrameDefault Python/bytecodes.c:3263
    msgspec#8 0x7f07a0a14e6c in _PyFunction_Vectorcall Objects/call.c:419
    msgspec#9 0x7f07a0a14e6c in _PyObject_FastCallDictTstate Objects/call.c:144
    msgspec#10 0x7f07a0a14e6c in _PyObject_Call_Prepend Objects/call.c:508
    msgspec#11 0x7f07a0ac3381 in slot_tp_call Objects/typeobject.c:8791
    msgspec#12 0x7f07a0a14206 in _PyObject_MakeTpCall Objects/call.c:240
    msgspec#13 0x7f07a098d20a in _PyEval_EvalFrameDefault Python/bytecodes.c:2715
    msgspec#14 0x7f07a0a14e6c in _PyFunction_Vectorcall Objects/call.c:419
    msgspec#15 0x7f07a0a14e6c in _PyObject_FastCallDictTstate Objects/call.c:144
    msgspec#16 0x7f07a0a14e6c in _PyObject_Call_Prepend Objects/call.c:508
    msgspec#17 0x7f07a0ac3381 in slot_tp_call Objects/typeobject.c:8791
    msgspec#18 0x7f07a0a1550e in PyObject_Call (/opt/hostedtoolcache/Python/3.12.15/x64/lib/libpython3.12.so.1.0+0x21550e) (BuildId: d5ce433e66c26342bfaf6b82ab1a95541a8ebade)
    msgspec#19 0x7f07a098d82f in _PyEval_EvalFrameDefault Python/bytecodes.c:3263
    msgspec#20 0x7f07a0a14e6c in _PyFunction_Vectorcall Objects/call.c:419
    msgspec#21 0x7f07a0a14e6c in _PyObject_FastCallDictTstate Objects/call.c:144
    msgspec#22 0x7f07a0a14e6c in _PyObject_Call_Prepend Objects/call.c:508
    msgspec#23 0x7f07a0ac3381 in slot_tp_call Objects/typeobject.c:8791
    msgspec#24 0x7f07a0a14206 in _PyObject_MakeTpCall Objects/call.c:240
    msgspec#25 0x7f07a098d20a in _PyEval_EvalFrameDefault Python/bytecodes.c:2715
    msgspec#26 0x7f07a0a14e6c in _PyFunction_Vectorcall Objects/call.c:419
    msgspec#27 0x7f07a0a14e6c in _PyObject_FastCallDictTstate Objects/call.c:144
    msgspec#28 0x7f07a0a14e6c in _PyObject_Call_Prepend Objects/call.c:508
    msgspec#29 0x7f07a0ac3381 in slot_tp_call Objects/typeobject.c:8791
    msgspec#30 0x7f07a0a14206 in _PyObject_MakeTpCall Objects/call.c:240
    msgspec#31 0x7f07a098d20a in _PyEval_EvalFrameDefault Python/bytecodes.c:2715
    msgspec#32 0x7f07a0a14e6c in _PyFunction_Vectorcall Objects/call.c:419
    msgspec#33 0x7f07a0a14e6c in _PyObject_FastCallDictTstate Objects/call.c:144
    msgspec#34 0x7f07a0a14e6c in _PyObject_Call_Prepend Objects/call.c:508
    msgspec#35 0x7f07a0ac3381 in slot_tp_call Objects/typeobject.c:8791
    msgspec#36 0x7f07a0a14206 in _PyObject_MakeTpCall Objects/call.c:240
    msgspec#37 0x7f07a098d20a in _PyEval_EvalFrameDefault Python/bytecodes.c:2715
    msgspec#38 0x7f07a0ae8f35 in PyEval_EvalCode Python/ceval.c:580
    msgspec#39 0x7f07a0ae7a3c in builtin_exec_impl Python/bltinmodule.c:1093
    msgspec#40 0x7f07a0ae7a3c in builtin_exec Python/clinic/bltinmodule.c.h:543
    msgspec#41 0x7f07a0a345a2 in cfunction_vectorcall_FASTCALL_KEYWORDS Objects/methodobject.c:438
    msgspec#42 0x7f07a0a14532 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:92
    msgspec#43 0x7f07a0a14532 in PyObject_Vectorcall Objects/call.c:325
    msgspec#44 0x7f07a098d20a in _PyEval_EvalFrameDefault Python/bytecodes.c:2715
    msgspec#45 0x7f07a0ae8f35 in PyEval_EvalCode Python/ceval.c:580
    msgspec#46 0x7f07a0b09f76 in run_eval_code_obj Python/pythonrun.c:1757
    msgspec#47 0x7f07a0b09aaa in run_mod Python/pythonrun.c:1778
    msgspec#48 0x7f07a0b099f1 in pyrun_file Python/pythonrun.c:1674
    msgspec#49 0x7f07a0b09852 in _PyRun_SimpleFileObject Python/pythonrun.c:459
    msgspec#50 0x7f07a0b09667 in _PyRun_AnyFileObject Python/pythonrun.c:78
    msgspec#51 0x7f07a0b12943 in pymain_run_file_obj Modules/main.c:361
    msgspec#52 0x7f07a0b12943 in pymain_run_file Modules/main.c:380
    msgspec#53 0x7f07a0b12943 in pymain_run_python Modules/main.c:634
    msgspec#54 0x7f07a0b12943 in Py_RunMain Modules/main.c:714
    msgspec#55 0x7f07a0b1243a in Py_BytesMain Modules/main.c:768
    msgspec#56 0x7f07a042a1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: a4a7992a8e66555c8141ab2a08a8465ff6e0ea65)
    msgspec#57 0x7f07a042a28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: a4a7992a8e66555c8141ab2a08a8465ff6e0ea65)
    msgspec#58 0x558976af7094 in _start (/opt/hostedtoolcache/Python/3.12.15/x64/bin/python3.12+0x1094) (BuildId: d664fd041bd9df9902c326eb80717fa04325aaaa)

0x50e000e8e350 is located 16 bytes inside of 152-byte region [0x50e000e8e340,0x50e000e8e3d8)
allocated by thread T0 here:
    #0 0x7f07a10fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    msgspec#1 0x7f07a0a7c2fc in gc_alloc Modules/gcmodule.c:2330
    msgspec#2 0x7f07a0a7c2fc in _PyObject_GC_NewVar Modules/gcmodule.c:2364

SUMMARY: AddressSanitizer: bad-free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52 in free
==2637==ABORTING
```

See
https://github.com/msgspec/msgspec/actions/runs/38243375356/job/114791328508

Now it is properly cleaned.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON support?

1 participant