Repository navigation
Add a built-in :jsonl (JSON Lines) format - #3029
Merged
Merged
Conversation
ericproulx
force-pushed
the
feature/jsonl-format
branch
from
October 10, 2026 23:02
79ef5de to
2e73bcd
Compare
Danger ReportNo issues found. |
dblock
reviewed
Oct 12, 2026
dblock
left a comment
Member
There was a problem hiding this comment.
The OPT_IN name is strange. All we are trying to do is to introduce a non-default content-type? Should it be some kind of known type instead?
ericproulx
force-pushed
the
feature/jsonl-format
branch
from
October 16, 2026 19:13
2e73bcd to
8a6de8c
Compare
Contributor
Author
I removed the OPT_IN |
ericproulx
force-pushed
the
feature/jsonl-format
branch
5 times, most recently
from
October 17, 2026 19:28
caef3d9 to
a463a13
Compare
`format :jsonl` serves JSON Lines: an Array renders one line per element and any other object one line, each as the JSON formatter renders it; a String is served as it is. An Enumerator body is streamed, each element rendered only as the server reads the body, so a large export never sits in memory whole. A formatter now signals that by handing back a Grape::ServeStream::StreamResponse, which the formatter middleware serves the way it serves `stream`. A JSON Lines request body parses into an Array of its lines' values, skipping blank lines, and stays in env['api.request.body'] like a JSON Array body. Errors render as one JSON line. :jsonl is kept out of ContentTypes::DEFAULTS, which every API that declares no content type negotiates. An API declares it like any other format, `content_type :jsonl, 'application/jsonl'` (or application/x-ndjson), before `format :jsonl`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ericproulx
force-pushed
the
feature/jsonl-format
branch
from
October 17, 2026 20:42
a463a13 to
bc9e103
Compare
dblock
approved these changes
Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds JSON Lines (jsonlines.org) as a built-in format: a formatter, a parser and an error formatter registered under
:jsonl. Like any format outside the defaults, an API declares it withcontent_type.Grape::Formatter::Jsonrenders it (so aGrape::PrecompiledJsonelement is served verbatim as its line). A top-level String is served as it is, taken to be JSON Lines already, as:serializable_hashdoes.Enumeratorbody renders lazily: each element is encoded only when the server reads the body, and each line goes out as its own chunk. To support this, a formatter may now return aGrape::ServeStream::StreamResponse, which the formatter middleware serves the same way it servesstream.streamitself is unchanged and still writes raw chunks.InvalidMessageBody).Design choices
ContentTypes::DEFAULTS.DEFAULTSis what every API that declares no content type negotiates. Addingjsonlthere would make all of those APIs answerAccept: application/jsonland/path.jsonlin JSON Lines, acceptapplication/jsonlbodies where they answer 415 today, and add the type to grape-swagger'sproduces. An API declares it withcontent_type :jsonl, ...like any other format outside the defaults, soformat :jsonlalone raisesMissingMimeType. No new mechanism letsformatskip that declaration.env['api.request.body']and not merged intoparams. Wrapping the lines under an invented key would introduce a convention nobody asked for. The README shows a one-line custom parser that wraps them for params validation.application/jsonl. No media type is registered for JSON Lines.application/jsonlmatches the format name and the.jsonlextension, and the README declares it; an API whose clients expectapplication/x-ndjsondeclares that instead.Upgrading
An app that already declared
content_type :jsonl, ...itself, without its own formatter, parser or error formatter for it, now gets Grape's. Its String bodies are still served as they are, but a request body now arrives as an Array instead of the raw String (parser :jsonl, nilkeeps the String), and errors render as a JSON line instead of throughdefault_error_formatter. Covered in UPGRADING.Caveats
rescue_fromand the response simply ends early. This is documented in the README, andstreambehaves the same way today.Rack::ETag(which Rails includes) buffers any body that does not respond toto_path, so it reads a streamed body in full. Rack 3'sETagonly digeststo_arybodies.streamis affected the same way.Test plan
spec/grape/formatter/jsonl_spec.rb,spec/grape/parser/jsonl_spec.rb,spec/grape/error_formatter/jsonl_spec.rb. The streaming spec drives the API through Rack and checks that nothing is rendered before the body is read and that each element arrives as its own chunk.null/falselines each fail at least one spec.bundle exec rake(RuboCop + full RSpec suite) passes on Ruby 4.0.7.gemfiles/rack_2_2.gemfile(Rack 2.2.24).🤖 Generated with Claude Code