From 24d5539d46196f5439b5283a39ca2d7b3786a0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=99=8E=E9=B8=A3?= Date: Sat, 11 Jul 2026 06:51:51 +0800 Subject: [PATCH] Clarify JsonFraming object-only behavior #3244 Motivation: JsonFraming.objectScanner documentation could be read as supporting arbitrary top-level JSON values even though it only frames objects. Modification: Clarify the object-only contract in Scala, Java, and internal docs, document NDJSON and BOM boundaries, and link nested JSON users to Pekko Connectors. Result: Users can distinguish object framing from general-purpose JSON parsing and choose the appropriate API. Tests: - Not run - docs only - sbt "stream / Compile / doc" (passed) - sbt -Dpekko.genjavadoc.enabled=true Javaunidoc/doc (passed) - sbt checkCodeStyle (passed) - sbt +headerCheckAll (passed) - git diff --check (passed) References: Fixes #3244 --- .../pekko/stream/impl/JsonObjectParser.scala | 9 +++++---- .../pekko/stream/javadsl/JsonFraming.scala | 14 +++++++++++--- .../pekko/stream/scaladsl/JsonFraming.scala | 16 ++++++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/stream/src/main/scala/org/apache/pekko/stream/impl/JsonObjectParser.scala b/stream/src/main/scala/org/apache/pekko/stream/impl/JsonObjectParser.scala index 369835419c1..f3431cb9096 100644 --- a/stream/src/main/scala/org/apache/pekko/stream/impl/JsonObjectParser.scala +++ b/stream/src/main/scala/org/apache/pekko/stream/impl/JsonObjectParser.scala @@ -52,8 +52,8 @@ import pekko.util.ByteString * INTERNAL API: Use [[pekko.stream.scaladsl.JsonFraming]] instead. * * **Mutable** framing implementation that given any number of [[pekko.util.ByteString]] chunks, can emit JSON objects contained within them. - * Typically JSON objects are separated by new-lines or commas, however a top-level JSON Array can also be understood and chunked up - * into valid JSON objects by this framing implementation. + * Typically JSON objects are separated by newlines or commas. A top-level JSON array is supported as a container + * of objects, but top-level non-object values are not supported. * * Leading whitespace between elements will be trimmed. */ @@ -90,7 +90,8 @@ import pekko.util.ByteString /** * Attempt to locate next complete JSON object in buffered ByteString and returns `Some(it)` if found. - * May throw a [[pekko.stream.scaladsl.Framing.FramingException]] if the contained JSON is invalid or max object size is exceeded. + * May throw a [[pekko.stream.scaladsl.Framing.FramingException]] if content outside an object is not an array + * delimiter, comma, or whitespace, or if the max object size is exceeded. */ def poll(): Option[ByteString] = try { @@ -112,7 +113,7 @@ import pekko.util.ByteString throw new FramingException(s"Invalid JSON encountered at position [$pos] of [${ByteString(buffer).utf8String}]") } - /** @return true if an entire valid JSON object was found, false otherwise */ + /** @return true if an entire JSON object was found, false otherwise */ private def seekObject(): Boolean = { completedObject = false val bufSize = buffer.length diff --git a/stream/src/main/scala/org/apache/pekko/stream/javadsl/JsonFraming.scala b/stream/src/main/scala/org/apache/pekko/stream/javadsl/JsonFraming.scala index 545715c828e..04f01a66e8b 100644 --- a/stream/src/main/scala/org/apache/pekko/stream/javadsl/JsonFraming.scala +++ b/stream/src/main/scala/org/apache/pekko/stream/javadsl/JsonFraming.scala @@ -17,11 +17,16 @@ import org.apache.pekko import pekko.NotUsed import pekko.util.ByteString -/** Provides JSON framing operators that can separate valid JSON objects from incoming [[pekko.util.ByteString]] objects. */ +/** Provides JSON framing operators that can separate JSON objects from incoming [[pekko.util.ByteString]] objects. */ object JsonFraming { /** - * Returns a Flow that implements a "brace counting" based framing operator for emitting valid JSON chunks. + * Returns a Flow that implements a "brace counting" based framing operator for emitting JSON objects. + * + * This operator only frames JSON objects; it is not a general-purpose JSON parser and does not validate the + * object contents. A top-level JSON array is supported only when all its elements are objects. Other top-level JSON + * values, such as strings, numbers, booleans, or `null`, are not supported. JSON Lines or NDJSON input is supported + * when every record is an object. A leading UTF-8 byte-order mark (BOM) must be removed before framing. * * Typical examples of data that one may want to frame using this operator include: * @@ -36,10 +41,13 @@ object JsonFraming { * {"id": 1}, {"id": 2}, [...], {"id": 999} * }}} * - * The framing works independently of formatting, i.e. it will still emit valid JSON elements even if two + * The framing works independently of formatting, i.e. it will still emit JSON objects even if two * elements are separated by multiple newlines or other whitespace characters. And of course is insensitive * (and does not impact the emitting frame) to the JSON object's internal formatting. * + * For streaming nested JSON structures, see + * Apache Pekko Connectors JSON support. + * * @param maximumObjectLength The maximum length of allowed frames while decoding. If the maximum length is exceeded * this Flow will fail the stream. */ diff --git a/stream/src/main/scala/org/apache/pekko/stream/scaladsl/JsonFraming.scala b/stream/src/main/scala/org/apache/pekko/stream/scaladsl/JsonFraming.scala index b77470ff70c..7f57d54425d 100644 --- a/stream/src/main/scala/org/apache/pekko/stream/scaladsl/JsonFraming.scala +++ b/stream/src/main/scala/org/apache/pekko/stream/scaladsl/JsonFraming.scala @@ -24,7 +24,7 @@ import pekko.stream.scaladsl.Framing.FramingException import pekko.stream.stage.{ GraphStageLogic, InHandler, OutHandler } import pekko.util.ByteString -/** Provides JSON framing operators that can separate valid JSON objects from incoming [[pekko.util.ByteString]] objects. */ +/** Provides JSON framing operators that can separate JSON objects from incoming [[pekko.util.ByteString]] objects. */ object JsonFraming { /** Thrown if upstream completes with a partial object in the buffer. */ @@ -32,8 +32,13 @@ object JsonFraming { extends FramingException(msg) /** - * Returns a Flow that implements a "brace counting" based framing operator for emitting valid JSON chunks. - * It scans the incoming data stream for valid JSON objects and returns chunks of ByteStrings containing only those valid chunks. + * Returns a Flow that implements a "brace counting" based framing operator for emitting JSON objects. + * It scans the incoming data stream for JSON objects and returns chunks of ByteStrings containing those objects. + * + * This operator only frames JSON objects; it is not a general-purpose JSON parser and does not validate the + * object contents. A top-level JSON array is supported only when all its elements are objects. Other top-level JSON + * values, such as strings, numbers, booleans, or `null`, are not supported. JSON Lines or NDJSON input is supported + * when every record is an object. A leading UTF-8 byte-order mark (BOM) must be removed before framing. * * Typical examples of data that one may want to frame using this operator include: * @@ -48,10 +53,13 @@ object JsonFraming { * {"id": 1}, {"id": 2}, [...], {"id": 999} * }}} * - * The framing works independently of formatting, i.e. it will still emit valid JSON elements even if two + * The framing works independently of formatting, i.e. it will still emit JSON objects even if two * elements are separated by multiple newlines or other whitespace characters. And of course is insensitive * (and does not impact the emitting frame) to the JSON object's internal formatting. * + * For streaming nested JSON structures, see + * Apache Pekko Connectors JSON support. + * * If the stream completes while mid-object, the stage will fail with a [[PartialObjectException]]. * * @param maximumObjectLength The maximum length of allowed frames while decoding. If the maximum length is exceeded