Skip to content

fix json emitter for empty response bodies #178

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

Merged
merged 5 commits into from
Jul 22, 2025
Merged
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
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.33.0
current_version = 0.34.0

[bumpversion:file:pyproject.toml]

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# [Unreleased]

# [0.34.0]
- [PR 178](https://github.com/salesforce/django-declarative-apis/pull/178) Fix JSONEmitter for empty response bodies.

# [0.33.0]
- [PR 170](https://github.com/salesforce/django-declarative-apis/pull/170) Update publish workflow
- [PR 173](https://github.com/salesforce/django-declarative-apis/pull/173) handle request.GET or request.POST being None + a few other improvements
Expand Down
20 changes: 12 additions & 8 deletions django_declarative_apis/resources/emitters.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,20 +182,24 @@ def decode(self, data):

def render(self, request):
cb = request.GET.get("callback", None)
assert cb is None, "JSONP Callbacks not suppoted"
seria = json.dumps(
self.decode(self.construct()),
cls=DjangoJSONEncoder,
ensure_ascii=False,
indent=4,
)
assert cb is None, "JSONP Callbacks not supported"
seria = self.decode(self.construct())
if isinstance(seria, list):
if len(seria) == 0 or (len(seria) == 1 and len(seria[0]) == 0):
Copy link
Collaborator

@bgrant bgrant Jul 22, 2025

Choose a reason for hiding this comment

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

I assume in some cases self.decode returns a tuple or something and that's the cause of the clause after the or? Is there a possibility of guessing this wrong and getting an IndexError on that seria[0]?

Copy link
Member Author

Choose a reason for hiding this comment

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

The base case (len == 0) is a just in case. What we actually see with our current issue is [""]. No, if we have a list of length 1, it would always be accessible and have a length given it's a string (val.decode("utf8") in self.decode)

# the body is empty, no need to run json.dumps
return ""

# Callback
# TODO: do we care about JSONP?
# if cb and is_valid_jsonp_callback_value(cb):
# return '%s(%s)' % (cb, seria)

return seria
return json.dumps(
seria,
cls=DjangoJSONEncoder,
ensure_ascii=False,
indent=4,
)


Emitter.register("json", JSONEmitter, "application/json; charset=utf-8")
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
# built documents.

# The full version, including alpha/beta/rc tags.
release = "0.33.0" # set by bumpversion
release = "0.34.0" # set by bumpversion

# The short X.Y version.
version = release.rsplit(".", 1)[0]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "django-declarative-apis"
version = "0.33.0" # set by bumpversion
version = "0.34.0" # set by bumpversion
description = "Simple, readable, declarative APIs for Django"
readme = "README.md"
dependencies = [
Expand Down
9 changes: 9 additions & 0 deletions tests/resources/test_emitters.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ def test_decode(self):
resp = em.render(django.test.RequestFactory().get("/"))
self.assertEqual(json.loads(resp), ["foo", "bar"])

def test_decode_empty_list(self):
em = emitters.JSONEmitter([""], lambda: None)
Copy link
Collaborator

Choose a reason for hiding this comment

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

for completeness don't we want another test with a truly empty list?

resp = em.render(django.test.RequestFactory().get("/"))
self.assertEqual(resp, "")

em = emitters.JSONEmitter([], lambda: None)
resp = em.render(django.test.RequestFactory().get("/"))
self.assertEqual(resp, "")


class DjangoEmitterTestCase(unittest.TestCase):
def test_render_http_response_succes(self):
Expand Down