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
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,24 @@ Django Rest Framework renderer using [ujson](https://github.com/esnme/ultrajson)

`pip install drf_ujson`

You can then set the `UJSONRenderer` class as your default renderer in your `settings.py`
You can then set the `UJSONRenderer` and `UJSONParser` classes as your default in your `settings.py`

```python
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'drf_ujson.renderers.UJSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
...
}
```

Also you can set the `UJSONParser` class as your default parser in your `settings.py`

```python
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'drf_ujson.parsers.UJSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
),
...
}
```


## Benchmarks
This is on average 2.3x faster than the default JSON Serializer.

Expand Down
21 changes: 10 additions & 11 deletions drf_ujson/parsers.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import codecs

from django.conf import settings
from rest_framework.compat import six
from rest_framework.parsers import BaseParser, ParseError
from rest_framework.renderers import JSONRenderer
from rest_framework import renderers
from rest_framework.settings import api_settings
import ujson


__author__ = 'y.gavenchuk aka murminathor'
__all__ = ['UJSONParser', ]


class UJSONParser(BaseParser):
"""
Parses JSON-serialized data by ujson parser.
Parses JSON-serialized data.
"""

media_type = 'application/json'
renderer_class = JSONRenderer
renderer_class = renderers.JSONRenderer
strict = api_settings.STRICT_JSON

def parse(self, stream, media_type=None, parser_context=None):
"""
Expand All @@ -28,7 +26,8 @@ def parse(self, stream, media_type=None, parser_context=None):
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)

try:
data = stream.read().decode(encoding)
return ujson.loads(data)
decoded_stream = codecs.getreader(encoding)(stream)
parse_constant = ujson.strict_constant if self.strict else None
return ujson.load(decoded_stream, parse_constant=parse_constant)
except ValueError as exc:
raise ParseError('JSON parse error - %s' % six.text_type(exc))
95 changes: 88 additions & 7 deletions drf_ujson/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,109 @@
from rest_framework.compat import six
from rest_framework.renderers import BaseRenderer
import ujson
from django.http.multipartparser import parse_header
from rest_framework.settings import api_settings


def zero_as_none(value):
return None if value == 0 else value


# class UJSONRenderer(BaseRenderer):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a huge amount of commented out code being committed?

# """
# Renderer which serializes to JSON.
# Applies JSON's backslash-u character escaping for non-ascii characters.
# Uses the blazing-fast ujson library for serialization.
# """
#
# media_type = 'application/json'
# format = 'json'
# ensure_ascii = True
# charset = None
#
# def get_indent(self, accepted_media_type, renderer_context):
# if accepted_media_type:
# # If the media type looks like 'application/json; indent=4',
# # then pretty print the result.
# # Note that we coerce `indent=0` into `indent=None`.
# base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
# try:
# return zero_as_none(max(min(int(params['indent']), 8), 0))
# except (KeyError, ValueError, TypeError):
# pass
#
# # If 'indent' is provided in the context, then pretty print the result.
# # E.g. If we're being called by the BrowsableAPIRenderer.
# return renderer_context.get('indent', None)
#
# def render(self, data, *args, **kwargs):
#
# if data is None:
# return bytes()
#
# ret = ujson.dumps(data, ensure_ascii=self.ensure_ascii)
#
# # force return value to unicode
# if isinstance(ret, six.text_type):
# return bytes(ret.encode('utf-8'))
# return ret


class UJSONRenderer(BaseRenderer):
"""
Renderer which serializes to JSON.
Applies JSON's backslash-u character escaping for non-ascii characters.
Uses the blazing-fast ujson library for serialization.
"""

media_type = 'application/json'
format = 'json'
ensure_ascii = True
ensure_ascii = not api_settings.UNICODE_JSON
compact = api_settings.COMPACT_JSON
strict = api_settings.STRICT_JSON

# We don't set a charset because JSON is a binary encoding,
# that can be encoded as utf-8, utf-16 or utf-32.
# See: https://www.ietf.org/rfc/rfc4627.txt
# Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/
charset = None

def render(self, data, *args, **kwargs):
def get_indent(self, accepted_media_type, renderer_context):
if accepted_media_type:
# If the media type looks like 'application/json; indent=4',
# then pretty print the result.
# Note that we coerce `indent=0` into `indent=None`.
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
try:
return zero_as_none(max(min(int(params['indent']), 8), 0))
except (KeyError, ValueError, TypeError):
pass

# If 'indent' is provided in the context, then pretty print the result.
# E.g. If we're being called by the BrowsableAPIRenderer.
return renderer_context.get('indent', None)

def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render `data` into JSON, returning a bytestring.
"""
if data is None:
return bytes()

ret = ujson.dumps(data, ensure_ascii=self.ensure_ascii)
renderer_context = renderer_context or {}
indent = self.get_indent(accepted_media_type, renderer_context)

ret = ujson.dumps(
data,
indent=indent, ensure_ascii=self.ensure_ascii,
)

# force return value to unicode
# On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
# but if ensure_ascii=False, the return type is underspecified,
# and may (or may not) be unicode.
# On python 3.x json.dumps() returns unicode strings.
if isinstance(ret, six.text_type):
# We always fully escape \u2028 and \u2029 to ensure we output JSON
# that is a strict javascript subset. If bytes were returned
# by json.dumps() then we don't have these characters in any case.
# See: http://timelessrepo.com/json-isnt-a-javascript-subset
ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029')
return bytes(ret.encode('utf-8'))
return ret