Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Changes
1.27 (unreleased)
~~~~~~~~~~~~~~~~~

* Expose the current RFC 6901 path through ``Decoder.pointer`` during decoder callbacks
(`issue #134`__), including recursive and concurrent decoder calls. Retain containers
while parsing into custom mappings that discard their values.

__ https://github.com/python-rapidjson/python-rapidjson/issues/134

* Preserve the value of non-ASCII ``RawJSON`` input with ``ensure_ascii=True`` by emitting
JSON Unicode escapes instead of truncating code points (`PR #258`__), thanks to Parijat
Biswas
Expand Down
28 changes: 28 additions & 0 deletions docs/decoder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@

The UUID mode, whether and how UUID literals will be recognized.

.. attribute:: pointer

:type: str or None

The JSON Pointer (:rfc:`6901`) identifying the value being processed by
:meth:`start_object`, :meth:`end_object`, :meth:`end_array` or :meth:`string`.
This read-only attribute is available during these callbacks; otherwise it
is ``None``. The document root is identified by the empty string. Object
keys escape ``~`` as ``~0`` and ``/`` as ``~1``, and array indexes start at
zero. Empty and duplicate object keys are preserved in the path.

The path describes the original JSON structure, before callback replacements.
It is constructed only when the attribute is read. A recursive call to the
same decoder exposes the inner callback's path and restores the outer one
when that callback returns. Calls in different threads have separate paths.

.. doctest::

>>> class LocatedStrings(Decoder):
... def string(self, value):
... return (self.pointer, value)
...
>>> located = LocatedStrings()
>>> located('{"a/b": ["first", "second"]}')
{'a/b': [('/a~1b/0', 'first'), ('/a~1b/1', 'second')]}
>>> located.pointer is None
True

.. rubric:: Methods

.. method:: __call__(json, *, chunk_size=65536)
Expand Down
103 changes: 94 additions & 9 deletions rapidjson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <algorithm>
#include <cmath>
#include <new>
#include <string>
#include <vector>

Expand Down Expand Up @@ -81,6 +82,7 @@ struct HandlerContext {
PyObject* object;
const char* key;
SizeType keyLength;
size_t nextIndex;
bool isObject;
bool keyValuePairs;
bool copiedKey;
Expand Down Expand Up @@ -802,7 +804,30 @@ float_from_string(const char* s, Py_ssize_t len)
}


struct PyHandler;


// Callback scopes form a per-thread stack, so recursive and concurrent calls to
// the same Decoder never replace each other's current location.
struct DecoderCallback {
static thread_local DecoderCallback* active;
PyHandler* handler;
DecoderCallback* previous;
bool handled;

DecoderCallback(PyHandler* h, bool valueHandled)
: handler(h), previous(active), handled(valueHandled)
{ active = this; }

~DecoderCallback() { active = previous; }
};


thread_local DecoderCallback* DecoderCallback::active = NULL;


struct PyHandler {
PyObject* decoder;
PyObject* decoderStartObject;
PyObject* decoderEndObject;
PyObject* decoderEndArray;
Expand All @@ -821,7 +846,8 @@ struct PyHandler {
unsigned dm,
unsigned um,
unsigned nm)
: decoderStartObject(NULL),
: decoder(decoder),
decoderStartObject(NULL),
decoderEndObject(NULL),
decoderEndArray(NULL),
decoderString(NULL),
Expand Down Expand Up @@ -869,7 +895,7 @@ struct PyHandler {

bool Handle(PyObject* value) {
if (root) {
const HandlerContext& current = stack.back();
HandlerContext& current = stack.back();

if (current.isObject) {
PyObject* key = PyUnicode_FromStringAndSize(current.key,
Expand Down Expand Up @@ -919,6 +945,7 @@ struct PyHandler {
if (rc == -1) {
return false;
}
current.nextIndex++;
}
} else {
root = value;
Expand Down Expand Up @@ -965,6 +992,7 @@ struct PyHandler {
bool key_value_pairs;

if (decoderStartObject != NULL) {
DecoderCallback callback(this, false);
mapping = PyObject_CallFunctionObjArgs(decoderStartObject, NULL);
if (mapping == NULL)
return false;
Expand All @@ -983,7 +1011,11 @@ struct PyHandler {
key_value_pairs = false;
}

// Retain the context's reference before Handle transfers its reference
// to the parent, which may be a custom mapping that discards the value.
Py_INCREF(mapping);
if (!Handle(mapping)) {
Py_DECREF(mapping);
return false;
}

Expand All @@ -992,9 +1024,8 @@ struct PyHandler {
ctx.keyValuePairs = key_value_pairs;
ctx.object = mapping;
ctx.key = NULL;
ctx.nextIndex = 0;
ctx.copiedKey = false;
Py_INCREF(mapping);

stack.push_back(ctx);

return true;
Expand All @@ -1018,6 +1049,7 @@ struct PyHandler {

PyObject* replacement;
if (decoderEndObject != NULL) {
DecoderCallback callback(this, true);
replacement = PyObject_CallFunctionObjArgs(decoderEndObject, mapping, NULL);
} else /* if (objectHook != NULL) */ {
replacement = PyObject_CallFunctionObjArgs(objectHook, mapping, NULL);
Expand Down Expand Up @@ -1114,17 +1146,18 @@ struct PyHandler {
return false;
}

Py_INCREF(list);
if (!Handle(list)) {
Py_DECREF(list);
return false;
}

HandlerContext ctx;
ctx.isObject = false;
ctx.object = list;
ctx.key = NULL;
ctx.nextIndex = 0;
ctx.copiedKey = false;
Py_INCREF(list);

stack.push_back(ctx);

return true;
Expand All @@ -1146,8 +1179,11 @@ struct PyHandler {
return true;
}

PyObject* replacement = PyObject_CallFunctionObjArgs(decoderEndArray, sequence,
NULL);
PyObject* replacement;
{
DecoderCallback callback(this, true);
replacement = PyObject_CallFunctionObjArgs(decoderEndArray, sequence, NULL);
}
Py_DECREF(sequence);
if (replacement == NULL)
return false;
Expand Down Expand Up @@ -1568,6 +1604,7 @@ struct PyHandler {
return false;

if (decoderString != NULL) {
DecoderCallback callback(this, false);
PyObject* replacement = PyObject_CallFunctionObjArgs(decoderString, value,
NULL);
Py_DECREF(value);
Expand All @@ -1590,6 +1627,47 @@ typedef struct {
} DecoderObject;


static PyObject*
decoder_pointer(PyObject* self, void*)
{
const DecoderCallback* callback = DecoderCallback::active;
while (callback != NULL && callback->handler->decoder != self)
callback = callback->previous;
if (callback == NULL)
Py_RETURN_NONE;

const std::vector<HandlerContext>& stack = callback->handler->stack;
try {
std::string pointer;
for (size_t i = 0; i < stack.size(); i++) {
const HandlerContext& ctx = stack[i];
pointer += '/';
if (ctx.isObject) {
for (SizeType j = 0; j < ctx.keyLength; j++) {
char ch = ctx.key[j];
if (ch == '~')
pointer += "~0";
else if (ch == '/')
pointer += "~1";
else
pointer += ch;
}
} else {
// Ancestor containers and end callbacks have already been
// appended; start_object/string refer to the next array item.
size_t index = ctx.nextIndex;
if (i + 1 < stack.size() || callback->handled)
index--;
pointer += std::to_string(index);
}
}
return PyUnicode_FromStringAndSize(pointer.data(), pointer.size());
} catch (const std::bad_alloc&) {
return PyErr_NoMemory();
}
}


PyDoc_STRVAR(loads_docstring,
"loads(string, *, object_hook=None, number_mode=None, datetime_mode=None,"
" uuid_mode=None, parse_mode=None, allow_nan=True)\n"
Expand Down Expand Up @@ -1882,6 +1960,13 @@ static PyMemberDef decoder_members[] = {
};


static PyGetSetDef decoder_getset[] = {
{"pointer", decoder_pointer, NULL,
"The current callback's JSON Pointer, or None outside callbacks.", NULL},
{NULL}
};


static PyTypeObject Decoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"rapidjson.Decoder", /* tp_name */
Expand Down Expand Up @@ -1912,7 +1997,7 @@ static PyTypeObject Decoder_Type = {
0, /* tp_iternext */
0, /* tp_methods */
decoder_members, /* tp_members */
0, /* tp_getset */
decoder_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
Expand Down
Loading
Loading