Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Don't convert json to Infinity/NaN #4918

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 60 additions & 1 deletion rest_framework/utils/encoders.py
Expand Up @@ -7,6 +7,8 @@
import decimal
import json
import uuid
from json.encoder import (INFINITY, _make_iterencode,
encode_basestring, encode_basestring_ascii)

from django.db.models.query import QuerySet
from django.utils import six, timezone
Expand All @@ -15,12 +17,68 @@

from rest_framework.compat import coreapi, total_seconds

try:
from json.encoder import FLOAT_REPR
except:
FLOAT_REPR = float.__repr__


class JSONEncoder(json.JSONEncoder):
"""
JSONEncoder subclass that knows how to encode date/time/timedelta,
decimal types, generators and other basic python objects.
"""

def iterencode(self, o, _one_shot=False):
"""Encode the given object and yield each string
representation as available.
For example::
for chunk in JSONEncoder().iterencode(bigobject):
mysocket.write(chunk)
"""
if self.check_circular:
markers = {}
else:
markers = None
if self.ensure_ascii:
_encoder = encode_basestring_ascii
else:
_encoder = encode_basestring
if six.PY2:
if self.encoding != 'utf-8':
def _encoder(o, _orig_encoder=_encoder,
_encoding=self.encoding):
if isinstance(o, str):
o = o.decode(_encoding)
return _orig_encoder(o)

def floatstr(o, allow_nan=self.allow_nan,
_repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
# Check for specials. Note that this type of test is processor
# and/or platform-specific, so do tests which don't depend on the
# internals.

if o != o:
text = '"NaN"'
elif o == _inf:
text = '"Infinity"'
elif o == _neginf:
text = '"-Infinity"'
else:
return _repr(o)

if not allow_nan:
raise ValueError(
"Out of range float values are not JSON compliant: " +
repr(o))

return text

return _make_iterencode(markers, self.default, _encoder, self.indent,
floatstr, self.key_separator,
self.item_separator, self.sort_keys,
self.skipkeys, _one_shot)(o, 0)

def default(self, obj):
# For Date Time string spec, see ECMA 262
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
Expand Down Expand Up @@ -55,7 +113,8 @@ def default(self, obj):
elif hasattr(obj, 'tolist'):
# Numpy arrays and array scalars.
return obj.tolist()
elif (coreapi is not None) and isinstance(obj, (coreapi.Document, coreapi.Error)):
elif (coreapi is not None) and isinstance(obj, (coreapi.Document,
coreapi.Error)):
raise RuntimeError(
'Cannot return a coreapi object from a JSON view. '
'You should be using a schema renderer instead for this view.'
Expand Down
28 changes: 28 additions & 0 deletions tests/test_encoders.py
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-

from datetime import date, datetime, timedelta
from decimal import Decimal
from uuid import uuid4

import pytest
from django.test import TestCase
from django.utils import six
from django.utils.timezone import utc

from rest_framework.compat import coreapi
Expand Down Expand Up @@ -92,3 +95,28 @@ def test_encode_object_with_tolist(self):
"""
foo = MockList()
assert self.encoder.default(foo) == [1, 2, 3]

def test_encode_float(self):
"""
Tests encoding floats with special values
"""

f = [3.141592653, float('inf'), float('-inf'), float('nan')]
assert self.encoder.encode(f) == '[3.141592653, "Infinity", "-Infinity", "NaN"]'

encoder = JSONEncoder(allow_nan=False)
try:
encoder.encode(f)
except ValueError:
pass
else:
assert False

def test_encode_string(self):
"""
Tests encoding string
"""

if six.PY2:
encoder2 = JSONEncoder(encoding='latin_1', check_circular=False)
assert encoder2.encode(['foo☺']) == '["foo\\u00e2\\u0098\\u00ba"]'