1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Diagnostics ;
4
5
using System . IO ;
5
6
using System . Runtime . CompilerServices ;
6
7
using System . Text . Json . Nodes ;
@@ -15,9 +16,9 @@ public abstract partial class JsonSerializerWrapper
15
16
public static JsonSerializerWrapper SpanSerializer { get ; } = new SpanSerializerWrapper ( ) ;
16
17
public static JsonSerializerWrapper StringSerializer { get ; } = new StringSerializerWrapper ( ) ;
17
18
public static StreamingJsonSerializerWrapper AsyncStreamSerializer { get ; } = new AsyncStreamSerializerWrapper ( ) ;
18
- public static StreamingJsonSerializerWrapper AsyncStreamSerializerWithSmallBuffer { get ; } = new AsyncStreamSerializerWrapper ( forceSmallBufferInOptions : true ) ;
19
+ public static StreamingJsonSerializerWrapper AsyncStreamSerializerWithSmallBuffer { get ; } = new AsyncStreamSerializerWrapper ( forceSmallBufferInOptions : true , forceBomInsertions : true ) ;
19
20
public static StreamingJsonSerializerWrapper SyncStreamSerializer { get ; } = new SyncStreamSerializerWrapper ( ) ;
20
- public static StreamingJsonSerializerWrapper SyncStreamSerializerWithSmallBuffer { get ; } = new SyncStreamSerializerWrapper ( forceSmallBufferInOptions : true ) ;
21
+ public static StreamingJsonSerializerWrapper SyncStreamSerializerWithSmallBuffer { get ; } = new SyncStreamSerializerWrapper ( forceSmallBufferInOptions : true , forceBomInsertions : true ) ;
21
22
public static JsonSerializerWrapper ReaderWriterSerializer { get ; } = new ReaderWriterSerializerWrapper ( ) ;
22
23
public static JsonSerializerWrapper DocumentSerializer { get ; } = new DocumentSerializerWrapper ( ) ;
23
24
public static JsonSerializerWrapper ElementSerializer { get ; } = new ElementSerializerWrapper ( ) ;
@@ -120,17 +121,22 @@ public override Task<object> DeserializeWrapper(string json, Type type, JsonSeri
120
121
private class AsyncStreamSerializerWrapper : StreamingJsonSerializerWrapper
121
122
{
122
123
private readonly bool _forceSmallBufferInOptions ;
124
+ private readonly bool _forceBomInsertions ;
123
125
124
126
public override bool IsAsyncSerializer => true ;
125
127
126
- public AsyncStreamSerializerWrapper ( bool forceSmallBufferInOptions = false )
128
+ public AsyncStreamSerializerWrapper ( bool forceSmallBufferInOptions = false , bool forceBomInsertions = false )
127
129
{
128
130
_forceSmallBufferInOptions = forceSmallBufferInOptions ;
131
+ _forceBomInsertions = forceBomInsertions ;
129
132
}
130
133
131
134
private JsonSerializerOptions ? ResolveOptionsInstance ( JsonSerializerOptions ? options )
132
135
=> _forceSmallBufferInOptions ? JsonSerializerOptionsSmallBufferMapper . ResolveOptionsInstanceWithSmallBuffer ( options ) : options ;
133
136
137
+ private Stream ResolveReadStream ( Stream stream )
138
+ => stream is not null && _forceBomInsertions ? new Utf8BomInsertingStream ( stream ) : stream ;
139
+
134
140
public override Task SerializeWrapper < T > ( Stream utf8Json , T value , JsonSerializerOptions options = null )
135
141
{
136
142
return JsonSerializer . SerializeAsync < T > ( utf8Json , value , ResolveOptionsInstance ( options ) ) ;
@@ -153,38 +159,43 @@ public override Task SerializeWrapper(Stream stream, object value, Type inputTyp
153
159
154
160
public override async Task < T > DeserializeWrapper < T > ( Stream utf8Json , JsonSerializerOptions options = null )
155
161
{
156
- return await JsonSerializer . DeserializeAsync < T > ( utf8Json , ResolveOptionsInstance ( options ) ) ;
162
+ return await JsonSerializer . DeserializeAsync < T > ( ResolveReadStream ( utf8Json ) , ResolveOptionsInstance ( options ) ) ;
157
163
}
158
164
159
165
public override async Task < object > DeserializeWrapper ( Stream utf8Json , Type returnType , JsonSerializerOptions options = null )
160
166
{
161
- return await JsonSerializer . DeserializeAsync ( utf8Json , returnType , ResolveOptionsInstance ( options ) ) ;
167
+ return await JsonSerializer . DeserializeAsync ( ResolveReadStream ( utf8Json ) , returnType , ResolveOptionsInstance ( options ) ) ;
162
168
}
163
169
164
170
public override async Task < T > DeserializeWrapper < T > ( Stream utf8Json , JsonTypeInfo < T > jsonTypeInfo )
165
171
{
166
- return await JsonSerializer . DeserializeAsync < T > ( utf8Json , jsonTypeInfo ) ;
172
+ return await JsonSerializer . DeserializeAsync < T > ( ResolveReadStream ( utf8Json ) , jsonTypeInfo ) ;
167
173
}
168
174
169
175
public override async Task < object > DeserializeWrapper ( Stream utf8Json , Type returnType , JsonSerializerContext context )
170
176
{
171
- return await JsonSerializer . DeserializeAsync ( utf8Json , returnType , context ) ;
177
+ return await JsonSerializer . DeserializeAsync ( ResolveReadStream ( utf8Json ) , returnType , context ) ;
172
178
}
173
179
}
174
180
175
181
private class SyncStreamSerializerWrapper : StreamingJsonSerializerWrapper
176
182
{
177
183
private readonly bool _forceSmallBufferInOptions ;
184
+ private readonly bool _forceBomInsertions ;
185
+
186
+ public override bool IsAsyncSerializer => false ;
178
187
179
- public SyncStreamSerializerWrapper ( bool forceSmallBufferInOptions = false )
188
+ public SyncStreamSerializerWrapper ( bool forceSmallBufferInOptions = false , bool forceBomInsertions = false )
180
189
{
181
190
_forceSmallBufferInOptions = forceSmallBufferInOptions ;
191
+ _forceBomInsertions = forceBomInsertions ;
182
192
}
183
193
184
194
private JsonSerializerOptions ? ResolveOptionsInstance ( JsonSerializerOptions ? options )
185
195
=> _forceSmallBufferInOptions ? JsonSerializerOptionsSmallBufferMapper . ResolveOptionsInstanceWithSmallBuffer ( options ) : options ;
186
196
187
- public override bool IsAsyncSerializer => false ;
197
+ private Stream ResolveReadStream ( Stream stream )
198
+ => stream is not null && _forceBomInsertions ? new Utf8BomInsertingStream ( stream ) : stream ;
188
199
189
200
public override Task SerializeWrapper < T > ( Stream utf8Json , T value , JsonSerializerOptions options = null )
190
201
{
@@ -212,25 +223,25 @@ public override Task SerializeWrapper(Stream stream, object value, Type inputTyp
212
223
213
224
public override Task < T > DeserializeWrapper < T > ( Stream utf8Json , JsonSerializerOptions options = null )
214
225
{
215
- T result = JsonSerializer . Deserialize < T > ( utf8Json , ResolveOptionsInstance ( options ) ) ;
226
+ T result = JsonSerializer . Deserialize < T > ( ResolveReadStream ( utf8Json ) , ResolveOptionsInstance ( options ) ) ;
216
227
return Task . FromResult ( result ) ;
217
228
}
218
229
219
230
public override Task < object > DeserializeWrapper ( Stream utf8Json , Type returnType , JsonSerializerOptions options = null )
220
231
{
221
- object result = JsonSerializer . Deserialize ( utf8Json , returnType , ResolveOptionsInstance ( options ) ) ;
232
+ object result = JsonSerializer . Deserialize ( ResolveReadStream ( utf8Json ) , returnType , ResolveOptionsInstance ( options ) ) ;
222
233
return Task . FromResult ( result ) ;
223
234
}
224
235
225
236
public override Task < T > DeserializeWrapper < T > ( Stream utf8Json , JsonTypeInfo < T > jsonTypeInfo )
226
237
{
227
- T result = JsonSerializer . Deserialize < T > ( utf8Json , jsonTypeInfo ) ;
238
+ T result = JsonSerializer . Deserialize < T > ( ResolveReadStream ( utf8Json ) , jsonTypeInfo ) ;
228
239
return Task . FromResult ( result ) ;
229
240
}
230
241
231
242
public override Task < object > DeserializeWrapper ( Stream utf8Json , Type returnType , JsonSerializerContext context )
232
243
{
233
- object result = JsonSerializer . Deserialize ( utf8Json , returnType , context ) ;
244
+ object result = JsonSerializer . Deserialize ( ResolveReadStream ( utf8Json ) , returnType , context ) ;
234
245
return Task . FromResult ( result ) ;
235
246
}
236
247
}
@@ -653,5 +664,88 @@ public static JsonSerializerOptions ResolveOptionsInstanceWithSmallBuffer(JsonSe
653
664
return smallBufferCopy ;
654
665
}
655
666
}
667
+
668
+ private sealed class Utf8BomInsertingStream : Stream
669
+ {
670
+ private const int Utf8BomLength = 3 ;
671
+ private readonly static byte [ ] s_utf8Bom = Encoding . UTF8 . GetPreamble ( ) ;
672
+
673
+ private readonly Stream _source ;
674
+ private byte [ ] ? _prefixBytes ;
675
+ private int _prefixBytesOffset = 0 ;
676
+ private int _prefixBytesCount = 0 ;
677
+
678
+ public Utf8BomInsertingStream ( Stream source )
679
+ {
680
+ Debug . Assert ( source . CanRead ) ;
681
+ _source = source ;
682
+ }
683
+
684
+ public override bool CanRead => _source . CanRead ;
685
+ public override bool CanSeek => false ;
686
+ public override bool CanWrite => false ;
687
+
688
+ public override int Read ( byte [ ] buffer , int offset , int count )
689
+ {
690
+ if ( _prefixBytes is null )
691
+ {
692
+ // This is the first read operation; read the first 3 bytes
693
+ // from the source to determine if it already includes a BOM.
694
+ // Only insert a BOM if it's missing from the source stream.
695
+
696
+ _prefixBytes = new byte [ 2 * Utf8BomLength ] ;
697
+ int bytesRead = ReadExactlyFromSource ( _prefixBytes , Utf8BomLength , Utf8BomLength ) ;
698
+
699
+ if ( _prefixBytes . AsSpan ( Utf8BomLength ) . SequenceEqual ( s_utf8Bom ) )
700
+ {
701
+ _prefixBytesOffset = Utf8BomLength ;
702
+ _prefixBytesCount = Utf8BomLength ;
703
+ }
704
+ else
705
+ {
706
+ s_utf8Bom . CopyTo ( _prefixBytes , 0 ) ;
707
+ _prefixBytesOffset = 0 ;
708
+ _prefixBytesCount = Utf8BomLength + bytesRead ;
709
+ }
710
+ }
711
+
712
+ int prefixBytesToWrite = Math . Min ( _prefixBytesCount , count ) ;
713
+ if ( prefixBytesToWrite > 0 )
714
+ {
715
+ _prefixBytes . AsSpan ( _prefixBytesOffset , prefixBytesToWrite ) . CopyTo ( buffer . AsSpan ( offset , count ) ) ;
716
+ _prefixBytesOffset += prefixBytesToWrite ;
717
+ _prefixBytesCount -= prefixBytesToWrite ;
718
+ offset += prefixBytesToWrite ;
719
+ count -= prefixBytesToWrite ;
720
+ }
721
+
722
+ return prefixBytesToWrite + _source . Read ( buffer , offset , count ) ;
723
+ }
724
+
725
+ private int ReadExactlyFromSource ( byte [ ] buffer , int offset , int count )
726
+ {
727
+ int totalRead = 0 ;
728
+
729
+ while ( totalRead < count )
730
+ {
731
+ int read = _source . Read ( buffer , offset + totalRead , count - totalRead ) ;
732
+ if ( read == 0 )
733
+ {
734
+ break ;
735
+ }
736
+
737
+ totalRead += read ;
738
+ }
739
+
740
+ return totalRead ;
741
+ }
742
+
743
+ public override long Length => throw new NotSupportedException ( ) ;
744
+ public override long Position { get => throw new NotSupportedException ( ) ; set => throw new NotSupportedException ( ) ; }
745
+ public override void Flush ( ) => throw new NotSupportedException ( ) ;
746
+ public override long Seek ( long offset , SeekOrigin origin ) => throw new NotSupportedException ( ) ;
747
+ public override void SetLength ( long value ) => throw new NotSupportedException ( ) ;
748
+ public override void Write ( byte [ ] buffer , int offset , int count ) => throw new NotSupportedException ( ) ;
749
+ }
656
750
}
657
751
}
0 commit comments