You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Let Utf8JsonReader process input with one complete JSON document per line #33030
Apologies if this ticket has been created but I missed it or there's a flag that I missed. I looked around but didn't find a good match.
I work in BigData and a common scenario is that we have large files that contains JSON documents separated by newline like so:
{}
[]
{}
This is not a valid JSON document obviously but it would help me a lot if the Utf8JsonReader could be configured to process a sequence of JSON documents like this.
My alternatives are to read line by line (forcing me to parse the data and thus losing performance) or adding extra complexity when iterating over the objects so I can keep track when an object is done and I should continue to the next.
Neither is very attractive to me and IMHO since this is a quite common scenario it would make some sense to add it, especially since the parser support relaxed parsing wrt to trailing commas and comments
Reacted by Steven, kscav, Angelo Breuer, Bouke Versteegh, Eric, Viktor Svub, Jacob Bundgaard, Duane King, Keith Williams, John Tur and 7 more
openSystemopenSystem.IOopenSystem.Text.Json[<EntryPoint>]letmain argv =letoptions= JsonReaderOptions ()letinput= File.ReadAllBytes "bigdata.json"letbytes= ReadOnlyMemory input
let mutableobjectsSeen=0let mutabletokensSeen=0for i =1to1000dolet recprocessAllLines offset =letbytes= bytes.Span
// Advance the span to next available objectletbytes= bytes.Slice offset
letreader= Utf8JsonReader (bytes, options)
objectsSeen <- objectsSeen +1let mutablelevel=0let mutablecont=true// Consume a single line that assumed to contain a single object// reader is a ref struct which prevents usage of tail-rec// :-(// Not really a bug in Utf8JsonReader but rather in the F#// story around ref structswhile cont && reader.Read ()do
tokensSeen <- tokensSeen +1match reader.TokenType with| JsonTokenType.StartArray
| JsonTokenType.StartObject -> level <- level +1| JsonTokenType.EndArray
| JsonTokenType.EndObject ->
level <- level -1if level =0then
cont <-false|_->()letoffset= offset + int reader.BytesConsumed
// After consuming the line there's still trailing non// printable lines, consume them in order to be able to// tell if we reached the end of inputlet recconsumeEndOfLine o =if o < input.Length && input.[o]<32uy then
consumeEndOfLine (o +1)else
o
letoffset= consumeEndOfLine offset
// Done?if offset < input.Length then
processAllLines offset
processAllLines 0
printfn "Objects seen : %d" objectsSeen
printfn "Tokens seen : %d" tokensSeen
0
... at the very least, using File.ReadLines(...) could help alleviate a large portion of your memory issues. What might be better for the workaround would be if there was some sort of File.ReadLinesAsBytes (since then you wouldn't be deserializing into string first) - essentially, something returning IEnumerable<ReadOnlySpan> that looks for newlines (or possibly something stranger, if you wanted to buffer the input).
I have a similar issue to this, and my issue may fit into this issue:
I am reading UTF-8 JSON data encapsulated in JavaScript from a web server. I've created a stream that skips to the JSON data (according to a specific preamble the JSON data is prefixed by). After the JsonDocument was read, I get an exception that there is data after the object's last token.
My current workaround is to save the entire web page in memory (about 2 MiB but I'm only interested in a few KiB) and put it into a JSON reader which works fine, e.g.:
varjsonData=Encoding.UTF8.GetBytes("{\"d\":\"test\"}}}}}");// A JSON object with additional data// Wanted wayusingvarmemoryStream=newMemoryStream(jsonData);// In my case this is a file stream or HTTP content streamvardocument=awaitJsonDocument.ParseAsync(memoryStream);// FAILS!!varjsonElement=document.RootElement;// The "normal" wayvardocument=JsonDocument.Parse(jsonData.AsMemory());// FAILS!!varjsonElement=document.RootElement;// Workaround:varjsonReader=newUtf8JsonReader(jsonData.AsSpan(),isFinalBlock:true,default);varjsonElement=(JsonElement)JsonSerializer.Deserialize<object>(refjsonReader);
In the first and second sample, I get the following exception: System.Text.Json.JsonReaderException : '}' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 12..
If I try to deserialize the object, it works fine, and additional data is ignored. The aforementioned is a considerable performance loss, as I have to read the web page and data I'm not interested in.
For example, a property AllowAdditionalContent in JsonDocumentOptions would be fine and would allow users to use System.Text.Json more flexibly.
I appreciate any help you can provide.
Angelo Breuer
usingvarmemoryStream=newMemoryStream(jsonData);// In my case this is a file stream or HTTP content stream`
If your stream is already skipping to the start, why not have it close at the end? Change the stream to look for the end of the JSON section, and returns EOF (Read returns 0) when it detects the end of the json section. If you don't have one, write a wrapping stream that does this.
// The "normal" wayvardocument=JsonDocument.Parse(jsonData.AsMemory());// FAILS!!
Something similar is possible here, since memory and sequence objects can take a range, allowing you to extract just that portion.
If your stream is already skipping to the start, why not have it close at the end? Change the stream to look for the end of the JSON section, and returns EOF (Read returns 0) when it detects the end of the JSON section. If you don't have one, write a wrapping stream that does this.
As a workaround, I've already done this but scanning each "block" of bytes for the specific end sequence where the JSON ends have significant overhead.
I have migrated from Json.NET where the aforementioned was possible without any additional overhead:
usingvarstreamReader=newStreamReader(someHttpResponseStream);// already skipped to start of JSONusingvarjsonTextReader=newJsonTextReader(streamReader);vardata=awaitJObject.LoadAsync(jsonTextReader);
I've also checked other JSON serializers which allow the same (Utf8Json, Json.NET, ServiceStack.Text).
Here is an API proposal based on a prototype I've been working on
Thanks, that looks pretty good.
For the JsonDeserializeAsyncEnumerableMode, would we imagine a future where you could opt-in to both RootLevelValue and Array, i.e. it'd yield top-level objects but if they were arrays it would instead enumerate their contents? I don't know how valuable that would be, but it would impact the shape of the enum.
would we imagine a future where you could opt-in to both RootLevelValue and Array, i.e. it'd yield top-level objects but if they were arrays it would instead enumerate their contents?
Probably not, and that's because of the ambiguity arising from the case where the element types themselves serialize as arrays. E.g. it would be unclear if DeserializeAsyncEnumerable<int[]>("""[]""") should be returning an empty or singleton IAE.
The property was removed from JsonSerializarOptions (and the sourcegen equivalent) because it creates slight confusion and isn't strictly necessary
The JsonDeserializeAsyncEnumerableMode seemed like it needs to be more complicated than just an enum. In lieu of designing an options type during the meeting, we accepted an overload with a boolean that opted into this new behavior.
Activity
mrange commentedon Mar 1, 2020
Code example of workaround:
layomia commentedon May 22, 2020
From @Int32Overflow in #36750
Clockwork-Muse commentedon May 22, 2020
... at the very least, using
File.ReadLines(...)could help alleviate a large portion of your memory issues. What might be better for the workaround would be if there was some sort ofFile.ReadLinesAsBytes(since then you wouldn't be deserializing intostringfirst) - essentially, something returningIEnumerable<ReadOnlySpan>that looks for newlines (or possibly something stranger, if you wanted to buffer the input).angelobreuer commentedon Oct 14, 2020
I have a similar issue to this, and my issue may fit into this issue:
I am reading UTF-8 JSON data encapsulated in JavaScript from a web server. I've created a stream that skips to the JSON data (according to a specific preamble the JSON data is prefixed by). After the JsonDocument was read, I get an exception that there is data after the object's last token.
My current workaround is to save the entire web page in memory (about 2 MiB but I'm only interested in a few KiB) and put it into a JSON reader which works fine, e.g.:
In the first and second sample, I get the following exception:
System.Text.Json.JsonReaderException : '}' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 12..If I try to deserialize the object, it works fine, and additional data is ignored. The aforementioned is a considerable performance loss, as I have to read the web page and data I'm not interested in.
For example, a property
AllowAdditionalContentin JsonDocumentOptions would be fine and would allow users to use System.Text.Json more flexibly.I appreciate any help you can provide.
Angelo Breuer
Clockwork-Muse commentedon Oct 14, 2020
@angelobreuer -
If your stream is already skipping to the start, why not have it close at the end? Change the stream to look for the end of the JSON section, and returns EOF (
Readreturns0) when it detects the end of the json section. If you don't have one, write a wrapping stream that does this.Something similar is possible here, since memory and sequence objects can take a range, allowing you to extract just that portion.
angelobreuer commentedon Oct 14, 2020
@Clockwork-Muse
As a workaround, I've already done this but scanning each "block" of bytes for the specific end sequence where the JSON ends have significant overhead.
I have migrated from Json.NET where the aforementioned was possible without any additional overhead:
I've also checked other JSON serializers which allow the same (Utf8Json, Json.NET, ServiceStack.Text).
eiriktsarpalis commentedon Oct 21, 2021
I think
SupportMultipleContent-like option would be a useful addition to Utf8JsonReader, but we'd need to write up an API proposal.20 remaining items
stephentoub commentedon Jul 1, 2024
Thanks, that looks pretty good.
For the JsonDeserializeAsyncEnumerableMode, would we imagine a future where you could opt-in to both RootLevelValue and Array, i.e. it'd yield top-level objects but if they were arrays it would instead enumerate their contents? I don't know how valuable that would be, but it would impact the shape of the enum.
eiriktsarpalis commentedon Jul 1, 2024
Probably not, and that's because of the ambiguity arising from the case where the element types themselves serialize as arrays. E.g. it would be unclear if
DeserializeAsyncEnumerable<int[]>("""[]""")should be returning an empty or singleton IAE.bartonjs commentedon Jul 2, 2024
Video
topLevelValues