• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

llnl / dftracer-utils / 23531027933

25 Mar 2026 08:05AM UTC coverage: 48.592% (-1.5%) from 50.098%
23531027933

Pull #57

github

web-flow
Merge d1070e289 into 38f9f3616
Pull Request #57: feat(comparator): add pairwise traces comparator

18900 of 49456 branches covered (38.22%)

Branch coverage included in aggregate %.

1604 of 1954 new or added lines in 25 files covered. (82.09%)

3407 existing lines in 135 files now uncovered.

18487 of 27485 relevant lines covered (67.26%)

240991.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.49
/src/dftracer/utils/python/json.cpp
1
#include <dftracer/utils/python/json.h>
2

3
#include <cstring>
4
#include <iostream>
5

6
static void JSON_dealloc(JSONObject* self) {
162✔
7
    if (self->doc && self->owns_doc) {
162!
8
        yyjson_doc_free(self->doc);
22✔
9
    }
22✔
10
    Py_TYPE(self)->tp_free((PyObject*)self);
162✔
11
}
162✔
12

13
static PyObject* JSON_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
×
14
    JSONObject* self;
15
    self = (JSONObject*)type->tp_alloc(type, 0);
×
16
    if (self != NULL) {
×
17
        self->doc = nullptr;
×
18
        self->root = nullptr;
×
19
        self->parsed = false;
×
20
        self->json_length = 0;
×
21
        self->owns_doc = false;
×
UNCOV
22
    }
×
23
    return (PyObject*)self;
×
24
}
25

26
static int JSON_init(JSONObject* self, PyObject* args, PyObject* kwds) {
×
27
    const char* json_str;
28
    if (!PyArg_ParseTuple(args, "s", &json_str)) {
×
29
        return -1;
×
30
    }
31

32
    self->json_length = strlen(json_str);
×
33
    if (self->json_length > 0) {
×
34
        std::memcpy(self->json_data, json_str, self->json_length);
×
UNCOV
35
    }
×
36
    self->doc = nullptr;
×
37
    self->root = nullptr;
×
38
    self->parsed = false;
×
39
    self->owns_doc = true;
×
40
    return 0;
×
UNCOV
41
}
×
42

43
static bool JSON_ensure_parsed(JSONObject* self) {
43✔
44
    if (self->root != nullptr) {
43!
45
        return true;
×
46
    }
47

48
    if (self->parsed && self->doc != nullptr) {
43!
49
        return true;
21✔
50
    }
51

52
    if (!self->parsed && self->json_length > 0) {
22!
53
        // Use YYJSON_READ_INSITU for large-scale processing
54
        // (zero-copy, in-place modification)
55
        yyjson_read_err err;
56
        self->doc = yyjson_read_opts(self->json_data, self->json_length,
22✔
57
                                     YYJSON_READ_INSITU, NULL, &err);
58
        if (!self->doc) {
22!
59
            char err_msg[256];
60
            std::snprintf(err_msg, sizeof(err_msg),
×
61
                          "Failed to parse JSON at position %zu: %s (code %u, "
62
                          "string: %.*s)",
63
                          err.pos, err.msg, err.code, (int)self->json_length,
×
64
                          self->json_data);
×
65
            PyErr_SetString(PyExc_ValueError, err_msg);
×
66
            return false;
×
67
        }
68
        self->parsed = true;
22✔
69
        return true;
22✔
70
    }
71

72
    // If we get here, there's no data to parse
73
    return false;
×
74
}
43✔
75

76
// Get the root yyjson_val for this JSON object (handles both top-level and
77
// subtrees)
78
static yyjson_val* JSON_get_root(JSONObject* self) {
43✔
79
    if (self->root != nullptr) {
43!
80
        // This is a subtree wrapper - return the wrapped value directly
81
        return self->root;
×
82
    }
83
    // This is a top-level document - get the root from the doc
84
    return yyjson_doc_get_root(self->doc);
43✔
85
}
43✔
86

87
static PyObject* JSON_contains(JSONObject* self, PyObject* key) {
31✔
88
    if (!JSON_ensure_parsed(self)) {
31!
89
        return NULL;
×
90
    }
91

92
    if (!PyUnicode_Check(key)) {
31!
93
        PyErr_SetString(PyExc_TypeError, "Key must be a string");
×
94
        return NULL;
×
95
    }
96

97
    const char* key_str = PyUnicode_AsUTF8(key);
31✔
98
    if (!key_str) {
31!
99
        return NULL;
×
100
    }
101

102
    yyjson_val* root = JSON_get_root(self);
31✔
103
    if (!yyjson_is_obj(root)) {
31!
104
        Py_RETURN_FALSE;
×
105
    }
106

107
    yyjson_val* val = yyjson_obj_get(root, key_str);
31✔
108
    if (val) {
31!
109
        Py_RETURN_TRUE;
31✔
110
    } else {
111
        Py_RETURN_FALSE;
×
112
    }
113
}
31✔
114

115
static int JSON_contains_sq(PyObject* self_obj, PyObject* key) {
31✔
116
    JSONObject* self = (JSONObject*)self_obj;
31✔
117
    PyObject* result = JSON_contains(self, key);
31✔
118
    if (!result) {
31!
119
        return -1;
×
120
    }
121

122
    int is_true = PyObject_IsTrue(result);
31✔
123
    Py_DECREF(result);
31✔
124
    return is_true;
31✔
125
}
31✔
126

127
static PyObject* yyjson_val_to_python(yyjson_val* val) {
12✔
128
    if (yyjson_is_null(val)) {
12!
129
        Py_RETURN_NONE;
×
130
    } else if (yyjson_is_bool(val)) {
12!
131
        if (yyjson_get_bool(val)) {
×
132
            Py_RETURN_TRUE;
×
133
        } else {
134
            Py_RETURN_FALSE;
×
135
        }
136
    } else if (yyjson_is_uint(val)) {
12!
137
        return PyLong_FromUnsignedLongLong(yyjson_get_uint(val));
×
138
    } else if (yyjson_is_int(val)) {
12!
139
        return PyLong_FromLongLong(yyjson_get_int(val));
×
140
    } else if (yyjson_is_real(val)) {
12!
141
        return PyFloat_FromDouble(yyjson_get_real(val));
×
142
    } else if (yyjson_is_str(val)) {
12!
143
        return PyUnicode_FromString(yyjson_get_str(val));
12✔
144
    } else if (yyjson_is_arr(val)) {
×
145
        std::size_t idx, max;
146
        yyjson_val* item;
147
        PyObject* list = PyList_New(0);
×
148
        if (!list) return NULL;
×
149

150
        yyjson_arr_foreach(val, idx, max, item) {
×
151
            PyObject* py_item = yyjson_val_to_python(item);
×
152
            if (!py_item) {
×
UNCOV
153
                Py_DECREF(list);
×
154
                return NULL;
×
155
            }
156
            if (PyList_Append(list, py_item) < 0) {
×
UNCOV
157
                Py_DECREF(py_item);
×
UNCOV
158
                Py_DECREF(list);
×
159
                return NULL;
×
160
            }
UNCOV
161
            Py_DECREF(py_item);
×
UNCOV
162
        }
×
163
        return list;
×
164
    } else if (yyjson_is_obj(val)) {
×
165
        std::size_t idx, max;
166
        yyjson_val *key_val, *val_val;
167
        PyObject* dict = PyDict_New();
×
168
        if (!dict) return NULL;
×
169

170
        yyjson_obj_foreach(val, idx, max, key_val, val_val) {
×
171
            const char* key_str = yyjson_get_str(key_val);
×
172
            PyObject* py_key = PyUnicode_FromString(key_str);
×
173
            PyObject* py_val = yyjson_val_to_python(val_val);
×
174

175
            if (!py_key || !py_val) {
×
176
                Py_XDECREF(py_key);
×
177
                Py_XDECREF(py_val);
×
UNCOV
178
                Py_DECREF(dict);
×
179
                return NULL;
×
180
            }
181

182
            if (PyDict_SetItem(dict, py_key, py_val) < 0) {
×
UNCOV
183
                Py_DECREF(py_key);
×
UNCOV
184
                Py_DECREF(py_val);
×
UNCOV
185
                Py_DECREF(dict);
×
186
                return NULL;
×
187
            }
188

UNCOV
189
            Py_DECREF(py_key);
×
UNCOV
190
            Py_DECREF(py_val);
×
UNCOV
191
        }
×
192
        return dict;
×
193
    }
194

195
    Py_RETURN_NONE;
×
196
}
12✔
197

198
static PyObject* JSON_getitem(JSONObject* self, PyObject* key) {
12✔
199
    if (!JSON_ensure_parsed(self)) {
12!
200
        return NULL;
×
201
    }
202

203
    if (!PyUnicode_Check(key)) {
12!
204
        PyErr_SetString(PyExc_TypeError, "Key must be a string");
×
205
        return NULL;
×
206
    }
207

208
    const char* key_str = PyUnicode_AsUTF8(key);
12✔
209
    if (!key_str) {
12!
210
        return NULL;
×
211
    }
212

213
    yyjson_val* root = JSON_get_root(self);
12✔
214
    if (!yyjson_is_obj(root)) {
12!
215
        PyErr_SetString(PyExc_TypeError, "JSON root is not an object");
×
216
        return NULL;
×
217
    }
218

219
    yyjson_val* val = yyjson_obj_get(root, key_str);
12✔
220
    if (!val) {
12!
221
        PyErr_SetString(PyExc_KeyError, key_str);
×
222
        return NULL;
×
223
    }
224

225
    // If the value is an object or array, return a lazy wrapper
226
    if (yyjson_is_obj(val) || yyjson_is_arr(val)) {
12!
227
        return JSON_from_yyjson_val(self->doc, val);
×
228
    }
229

230
    return yyjson_val_to_python(val);
12✔
231
}
12✔
232

233
static PyObject* JSON_keys(JSONObject* self, PyObject* Py_UNUSED(ignored)) {
×
234
    if (!JSON_ensure_parsed(self)) {
×
235
        return NULL;
×
236
    }
237

238
    yyjson_val* root = JSON_get_root(self);
×
239
    if (!yyjson_is_obj(root)) {
×
240
        return PyList_New(0);
×
241
    }
242

243
    PyObject* keys = PyList_New(0);
×
244
    if (!keys) return NULL;
×
245

246
    std::size_t idx, max;
247
    yyjson_val *key_val, *val_val;
248
    yyjson_obj_foreach(root, idx, max, key_val, val_val) {
×
249
        const char* key_str = yyjson_get_str(key_val);
×
250
        PyObject* py_key = PyUnicode_FromString(key_str);
×
251
        if (!py_key) {
×
UNCOV
252
            Py_DECREF(keys);
×
253
            return NULL;
×
254
        }
255
        if (PyList_Append(keys, py_key) < 0) {
×
UNCOV
256
            Py_DECREF(py_key);
×
UNCOV
257
            Py_DECREF(keys);
×
258
            return NULL;
×
259
        }
UNCOV
260
        Py_DECREF(py_key);
×
UNCOV
261
    }
×
262

263
    return keys;
×
UNCOV
264
}
×
265

266
static PyObject* JSON_values(JSONObject* self, PyObject* Py_UNUSED(ignored)) {
×
267
    if (!JSON_ensure_parsed(self)) {
×
268
        return NULL;
×
269
    }
270

271
    yyjson_val* root = JSON_get_root(self);
×
272
    if (!yyjson_is_obj(root)) {
×
273
        return PyList_New(0);
×
274
    }
275

276
    PyObject* values = PyList_New(0);
×
277
    if (!values) return NULL;
×
278

279
    std::size_t idx, max;
280
    yyjson_val *key_val, *val_val;
281
    yyjson_obj_foreach(root, idx, max, key_val, val_val) {
×
282
        PyObject* py_val;
283
        // If the value is an object or array, return a lazy wrapper
284
        if (yyjson_is_obj(val_val) || yyjson_is_arr(val_val)) {
×
285
            py_val = JSON_from_yyjson_val(self->doc, val_val);
×
UNCOV
286
        } else {
×
287
            py_val = yyjson_val_to_python(val_val);
×
288
        }
289

290
        if (!py_val) {
×
UNCOV
291
            Py_DECREF(values);
×
292
            return NULL;
×
293
        }
294

295
        if (PyList_Append(values, py_val) < 0) {
×
UNCOV
296
            Py_DECREF(py_val);
×
UNCOV
297
            Py_DECREF(values);
×
298
            return NULL;
×
299
        }
UNCOV
300
        Py_DECREF(py_val);
×
UNCOV
301
    }
×
302

303
    return values;
×
UNCOV
304
}
×
305

306
static PyObject* JSON_items(JSONObject* self, PyObject* Py_UNUSED(ignored)) {
×
307
    if (!JSON_ensure_parsed(self)) {
×
308
        return NULL;
×
309
    }
310

311
    yyjson_val* root = JSON_get_root(self);
×
312
    if (!yyjson_is_obj(root)) {
×
313
        return PyList_New(0);
×
314
    }
315

316
    PyObject* items = PyList_New(0);
×
317
    if (!items) return NULL;
×
318

319
    std::size_t idx, max;
320
    yyjson_val *key_val, *val_val;
321
    yyjson_obj_foreach(root, idx, max, key_val, val_val) {
×
322
        const char* key_str = yyjson_get_str(key_val);
×
323
        PyObject* py_key = PyUnicode_FromString(key_str);
×
324
        if (!py_key) {
×
UNCOV
325
            Py_DECREF(items);
×
326
            return NULL;
×
327
        }
328

329
        PyObject* py_val;
330
        // If the value is an object or array, return a lazy wrapper
331
        if (yyjson_is_obj(val_val) || yyjson_is_arr(val_val)) {
×
332
            py_val = JSON_from_yyjson_val(self->doc, val_val);
×
UNCOV
333
        } else {
×
334
            py_val = yyjson_val_to_python(val_val);
×
335
        }
336

337
        if (!py_val) {
×
UNCOV
338
            Py_DECREF(py_key);
×
UNCOV
339
            Py_DECREF(items);
×
340
            return NULL;
×
341
        }
342

343
        PyObject* tuple = PyTuple_Pack(2, py_key, py_val);
×
UNCOV
344
        Py_DECREF(py_key);
×
UNCOV
345
        Py_DECREF(py_val);
×
346

347
        if (!tuple) {
×
UNCOV
348
            Py_DECREF(items);
×
349
            return NULL;
×
350
        }
351

352
        if (PyList_Append(items, tuple) < 0) {
×
UNCOV
353
            Py_DECREF(tuple);
×
UNCOV
354
            Py_DECREF(items);
×
355
            return NULL;
×
356
        }
UNCOV
357
        Py_DECREF(tuple);
×
UNCOV
358
    }
×
359

360
    return items;
×
UNCOV
361
}
×
362

363
static PyObject* JSON_get(JSONObject* self, PyObject* args) {
×
364
    PyObject* key;
365
    PyObject* default_value = Py_None;
×
366

367
    if (!PyArg_ParseTuple(args, "O|O", &key, &default_value)) {
×
368
        return NULL;
×
369
    }
370

371
    if (!JSON_ensure_parsed(self)) {
×
372
        return NULL;
×
373
    }
374

375
    if (!PyUnicode_Check(key)) {
×
376
        PyErr_SetString(PyExc_TypeError, "Key must be a string");
×
377
        return NULL;
×
378
    }
379

380
    const char* key_str = PyUnicode_AsUTF8(key);
×
381
    if (!key_str) {
×
382
        return NULL;
×
383
    }
384

385
    yyjson_val* root = JSON_get_root(self);
×
386
    if (!yyjson_is_obj(root)) {
×
387
        Py_INCREF(default_value);
×
388
        return default_value;
×
389
    }
390

391
    yyjson_val* val = yyjson_obj_get(root, key_str);
×
392
    if (!val) {
×
393
        Py_INCREF(default_value);
×
394
        return default_value;
×
395
    }
396

397
    // If the value is an object or array, return a lazy wrapper
398
    if (yyjson_is_obj(val) || yyjson_is_arr(val)) {
×
399
        return JSON_from_yyjson_val(self->doc, val);
×
400
    }
401

402
    return yyjson_val_to_python(val);
×
UNCOV
403
}
×
404

405
// Helper function to recursively convert yyjson_val to Python dict/list
406
static PyObject* yyjson_val_to_python_deep(yyjson_val* val) {
×
407
    if (yyjson_is_null(val)) {
×
408
        Py_RETURN_NONE;
×
409
    } else if (yyjson_is_bool(val)) {
×
410
        if (yyjson_get_bool(val)) {
×
411
            Py_RETURN_TRUE;
×
412
        } else {
413
            Py_RETURN_FALSE;
×
414
        }
415
    } else if (yyjson_is_uint(val)) {
×
416
        return PyLong_FromUnsignedLongLong(yyjson_get_uint(val));
×
417
    } else if (yyjson_is_int(val)) {
×
418
        return PyLong_FromLongLong(yyjson_get_int(val));
×
419
    } else if (yyjson_is_real(val)) {
×
420
        return PyFloat_FromDouble(yyjson_get_real(val));
×
421
    } else if (yyjson_is_str(val)) {
×
422
        return PyUnicode_FromString(yyjson_get_str(val));
×
423
    } else if (yyjson_is_arr(val)) {
×
424
        std::size_t idx, max;
425
        yyjson_val* item;
426
        PyObject* list = PyList_New(0);
×
427
        if (!list) return NULL;
×
428

429
        yyjson_arr_foreach(val, idx, max, item) {
×
430
            PyObject* py_item = yyjson_val_to_python_deep(item);
×
431
            if (!py_item) {
×
UNCOV
432
                Py_DECREF(list);
×
433
                return NULL;
×
434
            }
435
            if (PyList_Append(list, py_item) < 0) {
×
UNCOV
436
                Py_DECREF(py_item);
×
UNCOV
437
                Py_DECREF(list);
×
438
                return NULL;
×
439
            }
UNCOV
440
            Py_DECREF(py_item);
×
UNCOV
441
        }
×
442
        return list;
×
443
    } else if (yyjson_is_obj(val)) {
×
444
        std::size_t idx, max;
445
        yyjson_val *key_val, *val_val;
446
        PyObject* dict = PyDict_New();
×
447
        if (!dict) return NULL;
×
448

449
        yyjson_obj_foreach(val, idx, max, key_val, val_val) {
×
450
            const char* key_str = yyjson_get_str(key_val);
×
451
            PyObject* py_key = PyUnicode_FromString(key_str);
×
452
            PyObject* py_val = yyjson_val_to_python_deep(val_val);
×
453

454
            if (!py_key || !py_val) {
×
455
                Py_XDECREF(py_key);
×
456
                Py_XDECREF(py_val);
×
UNCOV
457
                Py_DECREF(dict);
×
458
                return NULL;
×
459
            }
460

461
            if (PyDict_SetItem(dict, py_key, py_val) < 0) {
×
UNCOV
462
                Py_DECREF(py_key);
×
UNCOV
463
                Py_DECREF(py_val);
×
UNCOV
464
                Py_DECREF(dict);
×
465
                return NULL;
×
466
            }
467

UNCOV
468
            Py_DECREF(py_key);
×
UNCOV
469
            Py_DECREF(py_val);
×
UNCOV
470
        }
×
471
        return dict;
×
472
    }
473

474
    Py_RETURN_NONE;
×
UNCOV
475
}
×
476

477
static PyObject* JSON_unwrap(JSONObject* self, PyObject* Py_UNUSED(ignored)) {
×
478
    if (!JSON_ensure_parsed(self)) {
×
479
        return NULL;
×
480
    }
481

482
    yyjson_val* root = JSON_get_root(self);
×
483
    return yyjson_val_to_python_deep(root);
×
UNCOV
484
}
×
485

486
static PyObject* JSON_copy(JSONObject* self, PyObject* Py_UNUSED(ignored)) {
×
487
    if (!JSON_ensure_parsed(self)) {
×
488
        return NULL;
×
489
    }
490

491
    // If this is a subtree wrapper, create a new wrapper pointing to the same
492
    // subtree
493
    if (self->root != nullptr) {
×
494
        return JSON_from_yyjson_val(self->doc, self->root);
×
495
    }
496

497
    // For top-level documents, we need to serialize and re-parse since
498
    // the original json_data was modified in-place by YYJSON_READ_INSITU
499
    yyjson_val* root = JSON_get_root(self);
×
500
    if (root) {
×
501
        char* json_str = yyjson_val_write(root, 0, NULL);
×
502
        if (!json_str) {
×
503
            PyErr_SetString(PyExc_RuntimeError,
×
504
                            "Failed to serialize JSON for copy");
505
            return NULL;
×
506
        }
507

508
        size_t len = strlen(json_str);
×
509
        PyObject* result = JSON_from_data(json_str, len);
×
510
        free(json_str);
×
511
        return result;
×
512
    }
513

514
    // Empty object
515
    return JSON_from_data("{}", 2);
×
UNCOV
516
}
×
517

518
static PyObject* JSON_iter(JSONObject* self) {
×
519
    if (!JSON_ensure_parsed(self)) {
×
520
        return NULL;
×
521
    }
522

523
    yyjson_val* root = yyjson_doc_get_root(self->doc);
×
524
    if (!yyjson_is_obj(root)) {
×
525
        return PyObject_GetIter(PyList_New(0));
×
526
    }
527

528
    return PyObject_GetIter(JSON_keys(self, NULL));
×
UNCOV
529
}
×
530

531
static PyObject* JSON_str(JSONObject* self) {
×
532
    if (self->root != nullptr) {
×
533
        char* json_str = yyjson_val_write(self->root, 0, NULL);
×
534
        if (!json_str) {
×
535
            return PyUnicode_FromString("{}");
×
536
        }
537
        PyObject* result = PyUnicode_FromString(json_str);
×
538
        free(json_str);
×
539
        return result;
×
540
    }
541
    if (self->json_length > 0) {
×
542
        return PyUnicode_FromStringAndSize(self->json_data, self->json_length);
×
543
    }
544
    return PyUnicode_FromString("{}");
×
UNCOV
545
}
×
546

547
static PyObject* JSON_repr(JSONObject* self) {
×
548
    PyObject* str_obj = JSON_str(self);
×
549
    if (!str_obj) return NULL;
×
550
    PyObject* result = PyUnicode_FromFormat("JSON(%U)", str_obj);
×
UNCOV
551
    Py_DECREF(str_obj);
×
552
    return result;
×
UNCOV
553
}
×
554

555
static Py_ssize_t JSON_length(JSONObject* self) {
×
556
    if (!JSON_ensure_parsed(self)) {
×
557
        return -1;
×
558
    }
559

560
    yyjson_val* root = JSON_get_root(self);
×
561
    if (!yyjson_is_obj(root)) {
×
562
        return 0;
×
563
    }
564

565
    return (Py_ssize_t)yyjson_obj_size(root);
×
UNCOV
566
}
×
567

568
static int JSON_bool(JSONObject* self) {
×
569
    if (!JSON_ensure_parsed(self)) {
×
570
        return -1;
×
571
    }
572

573
    yyjson_val* root = JSON_get_root(self);
×
574
    if (!yyjson_is_obj(root)) {
×
575
        return 0;  // Non-objects are falsy
×
576
    }
577

578
    // Return true if object has at least one key
579
    return yyjson_obj_size(root) > 0 ? 1 : 0;
×
UNCOV
580
}
×
581

582
PyMethodDef JSON_methods[] = {{"__contains__", (PyCFunction)JSON_contains,
583
                               METH_O, "Check if key exists in JSON object"},
584
                              {"keys", (PyCFunction)JSON_keys, METH_NOARGS,
585
                               "Get all keys from JSON object"},
586
                              {"values", (PyCFunction)JSON_values, METH_NOARGS,
587
                               "Get all values from JSON object"},
588
                              {"items", (PyCFunction)JSON_items, METH_NOARGS,
589
                               "Get all key-value pairs from JSON object"},
590
                              {"get", (PyCFunction)JSON_get, METH_VARARGS,
591
                               "Get value by key with optional default"},
592
                              {"unwrap", (PyCFunction)JSON_unwrap, METH_NOARGS,
593
                               "Unwrap lazy JSON to native Python dict/list"},
594
                              {"copy", (PyCFunction)JSON_copy, METH_NOARGS,
595
                               "Return a shallow copy of the JSON object"},
596
                              {NULL}};
597

598
// gcc11_bandaid: Use positional initializers instead of designated
599
PySequenceMethods JSON_as_sequence = {
600
    NULL,            /* sq_length */
601
    NULL,            /* sq_concat */
602
    NULL,            /* sq_repeat */
603
    NULL,            /* sq_item */
604
    NULL,            /* was_sq_slice */
605
    NULL,            /* sq_ass_item */
606
    NULL,            /* was_sq_ass_slice */
607
    JSON_contains_sq /* sq_contains */
608
};
609

610
PyMappingMethods JSON_as_mapping = {
611
    (lenfunc)JSON_length,     /* mp_length */
612
    (binaryfunc)JSON_getitem, /* mp_subscript */
613
    NULL                      /* mp_ass_subscript */
614
};
615

616
PyNumberMethods JSON_as_number = {
617
    NULL,               /* nb_add */
618
    NULL,               /* nb_subtract */
619
    NULL,               /* nb_multiply */
620
    NULL,               /* nb_remainder */
621
    NULL,               /* nb_divmod */
622
    NULL,               /* nb_power */
623
    NULL,               /* nb_negative */
624
    NULL,               /* nb_positive */
625
    NULL,               /* nb_absolute */
626
    (inquiry)JSON_bool, /* nb_bool */
627
};
628

629
PyTypeObject JSONType = {
630
    PyVarObject_HEAD_INIT(NULL, 0) "json.JSON", /* tp_name */
631
    sizeof(JSONObject),                         /* tp_basicsize */
632
    0,                                          /* tp_itemsize */
633
    (destructor)JSON_dealloc,                   /* tp_dealloc */
634
    0,                                          /* tp_vectorcall_offset */
635
    0,                                          /* tp_getattr */
636
    0,                                          /* tp_setattr */
637
    0,                                          /* tp_as_async */
638
    (reprfunc)JSON_repr,                        /* tp_repr */
639
    &JSON_as_number,                            /* tp_as_number */
640
    &JSON_as_sequence,                          /* tp_as_sequence */
641
    &JSON_as_mapping,                           /* tp_as_mapping */
642
    0,                                          /* tp_hash */
643
    0,                                          /* tp_call */
644
    (reprfunc)JSON_str,                         /* tp_str */
645
    0,                                          /* tp_getattro */
646
    0,                                          /* tp_setattro */
647
    0,                                          /* tp_as_buffer */
648
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
649
    "JSON(json_str: str)\n"
650
    "--\n"
651
    "\n"
652
    "Lazy JSON object that parses on demand using yyjson.\n"
653
    "\n"
654
    "Args:\n"
655
    "    json_str (str): A JSON string to wrap. Parsing is deferred\n"
656
    "        until first attribute access.\n", /* tp_doc */
657
    0,                                         /* tp_traverse */
658
    0,                                         /* tp_clear */
659
    0,                                         /* tp_richcompare */
660
    0,                                         /* tp_weaklistoffset */
661
    (getiterfunc)JSON_iter,                    /* tp_iter */
662
    0,                                         /* tp_iternext */
663
    JSON_methods,                              /* tp_methods */
664
    0,                                         /* tp_members */
665
    0,                                         /* tp_getset */
666
    0,                                         /* tp_base */
667
    0,                                         /* tp_dict */
668
    0,                                         /* tp_descr_get */
669
    0,                                         /* tp_descr_set */
670
    0,                                         /* tp_dictoffset */
671
    (initproc)JSON_init,                       /* tp_init */
672
    0,                                         /* tp_alloc */
673
    JSON_new,                                  /* tp_new */
674
};
675

676
int init_json(PyObject* m) {
1✔
677
    if (PyType_Ready(&JSONType) < 0) return -1;
1!
678

679
    Py_INCREF(&JSONType);
1✔
680
    if (PyModule_AddObject(m, "JSON", (PyObject*)&JSONType) < 0) {
1!
UNCOV
681
        Py_DECREF(&JSONType);
×
UNCOV
682
        Py_DECREF(m);
×
683
        return -1;
×
684
    }
685

686
    return 0;
1✔
687
}
1✔
688

689
PyObject* JSON_from_data(const char* data, size_t length) {
162✔
690
    JSONObject* self =
162✔
691
        (JSONObject*)PyObject_MALLOC(sizeof(JSONObject) + length + 1);
162✔
692
    if (!self) {
162!
693
        return PyErr_NoMemory();
×
694
    }
695

696
    PyObject_INIT(self, &JSONType);
162✔
697

698
    self->doc = nullptr;
162✔
699
    self->root = nullptr;
162✔
700
    self->parsed = false;
162✔
701
    self->json_length = length;
162✔
702
    self->owns_doc = true;
162✔
703

704
    std::memcpy(self->json_data, data, length);
162✔
705
    self->json_data[length] = '\0';
162✔
706

707
    return (PyObject*)self;
162✔
708
}
162✔
709

710
// Create a JSON object wrapping a yyjson_val
711
// subtree (lazy wrapper for nested objects/arrays)
712
PyObject* JSON_from_yyjson_val(yyjson_doc* doc, yyjson_val* root) {
×
713
    JSONObject* self = (JSONObject*)PyObject_MALLOC(sizeof(JSONObject));
×
714
    if (!self) {
×
715
        return PyErr_NoMemory();
×
716
    }
717

718
    PyObject_INIT(self, &JSONType);
×
719

720
    self->doc = doc;         // Share the document (don't copy)
×
721
    self->root = root;       // Point to the subtree
×
722
    self->parsed = true;     // Already parsed (just wrapping a subtree)
×
723
    self->json_length = 0;   // No raw JSON data
×
724
    self->owns_doc = false;  // Don't free the doc (it's owned by parent)
×
725

726
    return (PyObject*)self;
×
UNCOV
727
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc