@@ -3,13 +3,12 @@ package io.github.quafadas.scautable.json
33import scala .io .Source
44import java .io .{StringReader , InputStream , InputStreamReader }
55import jakarta .json .Json
6- import jakarta .json .stream .{ JsonParser => JParser }
6+ import jakarta .json .stream .JsonParser as JParser
77import jakarta .json .stream .JsonParser .Event
88
99/** Streaming JSON parser using Java's JSON-P (JSR 374) streaming API.
1010 *
11- * This parser uses the native Java JSON streaming API to read JSON arrays
12- * incrementally without loading the entire array into memory.
11+ * This parser uses the native Java JSON streaming API to read JSON arrays incrementally without loading the entire array into memory.
1312 */
1413private [json] object StreamingJsonParser :
1514
@@ -21,22 +20,22 @@ private[json] object StreamingJsonParser:
2120 case class JsonBool (value : Boolean ) extends JsonValue
2221 case class JsonObject (fields : Map [String , JsonValue ]) extends JsonValue
2322
24- /** Parse JSON objects from an InputStream one at a time using Java's streaming API .
23+ /** Creates an iterator over JSON objects from a parser .
2524 *
26- * @param input The input stream to read from
27- * @param maxObjects Maximum number of objects to read (for type inference)
28- * @return Iterator of JSON objects
25+ * @param parser
26+ * The JSON-P parser to read from
27+ * @param maxObjects
28+ * Maximum number of objects to read (for type inference)
29+ * @return
30+ * Iterator of JSON objects
2931 */
30- def parseArrayStream (input : InputStream , maxObjects : Int = Int .MaxValue ): Iterator [JsonObject ] =
31- val reader = new InputStreamReader (input, " UTF-8" )
32- val parser = Json .createParser(reader)
33-
32+ private def createIterator (parser : JParser , maxObjects : Int ): Iterator [JsonObject ] =
3433 new Iterator [JsonObject ]:
3534 private var objectsRead = 0
3635 private var arrayStarted = false
3736 private var arrayEnded = false
3837 private var nextObj : Option [JsonObject ] = None
39-
38+
4039 // Skip to array start
4140 if ! arrayEnded && ! arrayStarted then
4241 while parser.hasNext && ! arrayStarted do
@@ -56,16 +55,19 @@ private[json] object StreamingJsonParser:
5655
5756 def next (): JsonObject =
5857 if ! hasNext then throw new NoSuchElementException (" No more objects" )
58+ end if
5959 val result = nextObj.get
6060 nextObj = None
6161 objectsRead += 1
6262 result
63+ end next
6364
6465 private def tryReadNext (): Option [JsonObject ] =
65- if arrayEnded || ! parser.hasNext then
66+ if arrayEnded || ! parser.hasNext then
6667 arrayEnded = true
6768 return None
68-
69+ end if
70+
6971 try
7072 parser.next() match
7173 case Event .START_OBJECT =>
@@ -77,14 +79,17 @@ private[json] object StreamingJsonParser:
7779 // Skip other events
7880 tryReadNext()
7981 catch
82+ case e : UnsupportedOperationException =>
83+ throw e // Re-throw nesting errors
8084 case e : Exception =>
8185 arrayEnded = true
8286 None
87+ end try
8388 end tryReadNext
8489
8590 private def readObject (parser : JParser ): JsonObject =
8691 val fields = scala.collection.mutable.Map [String , JsonValue ]()
87-
92+
8893 var continue = true
8994 while continue && parser.hasNext do
9095 parser.next() match
@@ -96,13 +101,15 @@ private[json] object StreamingJsonParser:
96101 continue = false
97102 case _ =>
98103 ()
99-
104+ end while
105+
100106 JsonObject (fields.toMap)
101107 end readObject
102108
103109 private def readValue (parser : JParser ): JsonValue =
104110 if ! parser.hasNext then return JsonNull
105-
111+ end if
112+
106113 parser.next() match
107114 case Event .VALUE_NULL =>
108115 JsonNull
@@ -115,160 +122,45 @@ private[json] object StreamingJsonParser:
115122 case Event .VALUE_NUMBER =>
116123 JsonNumber (parser.getBigDecimal())
117124 case Event .START_ARRAY =>
118- // Skip arrays for now
119- skipArray(parser)
120- JsonString ( " [] " )
125+ throw new UnsupportedOperationException (
126+ " Nested arrays are not supported. JSON must be a flat array of objects with primitive values only. "
127+ )
121128 case Event .START_OBJECT =>
122- // Skip nested objects for now
123- skipObject(parser)
124- JsonString ( " {} " )
129+ throw new UnsupportedOperationException (
130+ " Nested objects are not supported. JSON must be a flat array of objects with primitive values only. "
131+ )
125132 case other =>
126133 JsonNull
134+ end match
127135 end readValue
128-
129- private def skipArray (parser : JParser ): Unit =
130- var depth = 1
131- while depth > 0 && parser.hasNext do
132- parser.next() match
133- case Event .START_ARRAY => depth += 1
134- case Event .END_ARRAY => depth -= 1
135- case _ => ()
136-
137- private def skipObject (parser : JParser ): Unit =
138- var depth = 1
139- while depth > 0 && parser.hasNext do
140- parser.next() match
141- case Event .START_OBJECT => depth += 1
142- case Event .END_OBJECT => depth -= 1
143- case _ => ()
144136 end new
137+ end createIterator
138+
139+ /** Parse JSON objects from an InputStream one at a time using Java's streaming API.
140+ *
141+ * @param input
142+ * The input stream to read from
143+ * @param maxObjects
144+ * Maximum number of objects to read (for type inference)
145+ * @return
146+ * Iterator of JSON objects
147+ */
148+ def parseArrayStream (input : InputStream , maxObjects : Int = Int .MaxValue ): Iterator [JsonObject ] =
149+ val parser = Json .createParser(new InputStreamReader (input, " UTF-8" ))
150+ createIterator(parser, maxObjects)
145151 end parseArrayStream
146152
147153 /** Parse JSON from a Source (for compile-time use) */
148154 def parseArrayFromSource (source : Source , maxObjects : Int = Int .MaxValue ): Iterator [JsonObject ] =
149155 val content = source.mkString
150156 val input = new java.io.ByteArrayInputStream (content.getBytes(" UTF-8" ))
151157 parseArrayStream(input, maxObjects)
158+ end parseArrayFromSource
152159
153160 /** Parse JSON from a string */
154161 def parseArrayFromString (json : String , maxObjects : Int = Int .MaxValue ): Iterator [JsonObject ] =
155- val reader = new StringReader (json)
156- val parser = Json .createParser(reader)
157-
158- // Same implementation as parseArrayStream but with a Reader
159- val iter = new Iterator [JsonObject ]:
160- private var objectsRead = 0
161- private var arrayStarted = false
162- private var arrayEnded = false
163- private var nextObj : Option [JsonObject ] = None
164-
165- // Skip to array start
166- if ! arrayEnded && ! arrayStarted then
167- while parser.hasNext && ! arrayStarted do
168- parser.next() match
169- case Event .START_ARRAY =>
170- arrayStarted = true
171- case _ =>
172- ()
173- end if
174-
175- def hasNext : Boolean =
176- if arrayEnded || objectsRead >= maxObjects then false
177- else if nextObj.isDefined then true
178- else
179- nextObj = tryReadNext()
180- nextObj.isDefined
181-
182- def next (): JsonObject =
183- if ! hasNext then throw new NoSuchElementException (" No more objects" )
184- val result = nextObj.get
185- nextObj = None
186- objectsRead += 1
187- result
188-
189- private def tryReadNext (): Option [JsonObject ] =
190- if arrayEnded || ! parser.hasNext then
191- arrayEnded = true
192- return None
193-
194- try
195- parser.next() match
196- case Event .START_OBJECT =>
197- Some (readObject(parser))
198- case Event .END_ARRAY =>
199- arrayEnded = true
200- None
201- case _ =>
202- // Skip other events
203- tryReadNext()
204- catch
205- case e : Exception =>
206- arrayEnded = true
207- None
208- end tryReadNext
209-
210- private def readObject (parser : JParser ): JsonObject =
211- val fields = scala.collection.mutable.Map [String , JsonValue ]()
212-
213- var continue = true
214- while continue && parser.hasNext do
215- parser.next() match
216- case Event .KEY_NAME =>
217- val key = parser.getString()
218- val value = readValue(parser)
219- fields(key) = value
220- case Event .END_OBJECT =>
221- continue = false
222- case _ =>
223- ()
224-
225- JsonObject (fields.toMap)
226- end readObject
227-
228- private def readValue (parser : JParser ): JsonValue =
229- if ! parser.hasNext then return JsonNull
230-
231- parser.next() match
232- case Event .VALUE_NULL =>
233- JsonNull
234- case Event .VALUE_TRUE =>
235- JsonBool (true )
236- case Event .VALUE_FALSE =>
237- JsonBool (false )
238- case Event .VALUE_STRING =>
239- JsonString (parser.getString())
240- case Event .VALUE_NUMBER =>
241- JsonNumber (parser.getBigDecimal())
242- case Event .START_ARRAY =>
243- // Skip arrays for now
244- skipArray(parser)
245- JsonString (" []" )
246- case Event .START_OBJECT =>
247- // Skip nested objects for now
248- skipObject(parser)
249- JsonString (" {}" )
250- case other =>
251- JsonNull
252- end readValue
253-
254- private def skipArray (parser : JParser ): Unit =
255- var depth = 1
256- while depth > 0 && parser.hasNext do
257- parser.next() match
258- case Event .START_ARRAY => depth += 1
259- case Event .END_ARRAY => depth -= 1
260- case _ => ()
261-
262- private def skipObject (parser : JParser ): Unit =
263- var depth = 1
264- while depth > 0 && parser.hasNext do
265- parser.next() match
266- case Event .START_OBJECT => depth += 1
267- case Event .END_OBJECT => depth -= 1
268- case _ => ()
269- end new
270-
271- iter
162+ val parser = Json .createParser(new StringReader (json))
163+ createIterator(parser, maxObjects)
272164 end parseArrayFromString
273165
274166end StreamingJsonParser
0 commit comments