System.Text.Json에서 Utf8JsonReader를 사용하는 방법
이 문서에서는 사용자 지정 파서 및 역직렬 변환기를 빌드하는 데 Utf8JsonReader 형식을 사용하는 방법을 보여 줍니다.
Utf8JsonReader 는 UTF-8로 인코딩된 JSON 텍스트에 대한 고성능, 낮은 할당, 전달 전용 판독기입니다. 텍스트는 a ReadOnlySpan<byte>
또는 ReadOnlySequence<byte>
.에서 읽습니다. Utf8JsonReader
는 사용자 지정 파서 및 역직렬 변환기를 빌드하는 데 사용할 수 있는 하위 수준 형식입니다. (메서드는 JsonSerializer.Deserialize 커버 아래에 사용됩니다 Utf8JsonReader
.)
다음 예제에서는 클래스를 사용하는 Utf8JsonReader 방법을 보여줍니다. 이 코드는 변수가 jsonUtf8Bytes
UTF-8로 인코딩된 유효한 JSON을 포함하는 바이트 배열이라고 가정합니다.
var options = new JsonReaderOptions
{
AllowTrailingCommas = true,
CommentHandling = JsonCommentHandling.Skip
};
var reader = new Utf8JsonReader(jsonUtf8Bytes, options);
while (reader.Read())
{
Console.Write(reader.TokenType);
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
case JsonTokenType.String:
{
string? text = reader.GetString();
Console.Write(" ");
Console.Write(text);
break;
}
case JsonTokenType.Number:
{
int intValue = reader.GetInt32();
Console.Write(" ");
Console.Write(intValue);
break;
}
// Other token types elided for brevity
}
Console.WriteLine();
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
참고 항목
Utf8JsonReader
는 Visual Basic 코드에서 직접 사용할 수 없습니다. 자세한 내용은 Visual Basic 지원을 참조하세요.
Utf8JsonReader
를 사용하여 데이터 필터링
다음 예제는 파일을 동기적으로 읽고 값을 검색하는 방법을 보여줍니다.
using System.Text;
using System.Text.Json;
namespace SystemTextJsonSamples
{
public class Utf8ReaderFromFile
{
private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
private static ReadOnlySpan<byte> Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF };
public static void Run()
{
// ReadAllBytes if the file encoding is UTF-8:
string fileName = "UniversitiesUtf8.json";
ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
// Read past the UTF-8 BOM bytes if a BOM exists.
if (jsonReadOnlySpan.StartsWith(Utf8Bom))
{
jsonReadOnlySpan = jsonReadOnlySpan.Slice(Utf8Bom.Length);
}
// Or read as UTF-16 and transcode to UTF-8 to convert to a ReadOnlySpan<byte>
//string fileName = "Universities.json";
//string jsonString = File.ReadAllText(fileName);
//ReadOnlySpan<byte> jsonReadOnlySpan = Encoding.UTF8.GetBytes(jsonString);
int count = 0;
int total = 0;
var reader = new Utf8JsonReader(jsonReadOnlySpan);
while (reader.Read())
{
JsonTokenType tokenType = reader.TokenType;
switch (tokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
// Assume valid JSON, known schema
reader.Read();
if (reader.GetString()!.EndsWith("University"))
{
count++;
}
}
break;
}
}
Console.WriteLine($"{count} out of {total} have names that end with 'University'");
}
}
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
앞의 코드가 하는 역할은 다음과 같습니다.
JSON에 개체 배열이 포함되어 있고 각 개체에 문자열 형식의 "name" 속성이 포함될 수 있다고 가정합니다.
"대학교"로 끝나는 개체 및 "이름" 속성 값의 개수를 계산합니다.
파일이 UTF-16으로 인코딩되고 UTF-8로 코드 변환된다고 가정합니다.
UTF-8로 인코딩된 파일은 다음 코드를 사용하여
ReadOnlySpan<byte>
로 직접 읽을 수 있습니다.ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
파일에 UTF-8 BOM(바이트 순서 표시)이 포함된 경우 제거한 후 바이트를
Utf8JsonReader
에 전달해야 합니다. 판독기에는 텍스트가 필요하기 때문입니다. 그렇지 않으면 BOM은 잘못된 JSON으로 간주되고, 판독기에서 예외를 throw합니다.
다음은 위의 코드에서 읽을 수 있는 JSON 샘플입니다. 다음과 같이 "4개 이름 중 2개가 '대학교'로 끝나는 이름"이라는 결과 요약 메시지가 표시됩니다.
[
{
"web_pages": [ "https://contoso.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contoso.edu" ],
"name": "Contoso Community College"
},
{
"web_pages": [ "http://fabrikam.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikam.edu" ],
"name": "Fabrikam Community College"
},
{
"web_pages": [ "http://www.contosouniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contosouniversity.edu" ],
"name": "Contoso University"
},
{
"web_pages": [ "http://www.fabrikamuniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikamuniversity.edu" ],
"name": "Fabrikam University"
}
]
팁
이 예제의 비동기 버전은 .NET 샘플 JSON 프로젝트를 참조하세요.
Utf8JsonReader
를 사용하여 스트림에서 읽기
큰 파일(예: 크기가 기가바이트 이상)을 읽을 때 전체 파일을 한 번에 메모리에 로드하지 않도록 할 수 있습니다. 이 시나리오에서는 FileStream을 사용할 수 있습니다.
Utf8JsonReader
를 사용하여 스트림에서 읽는 경우 다음 규칙이 적용됩니다.
- 부분 JSON 페이로드를 포함하는 버퍼는 판독기가 진행할 수 있도록 최소한 그 안에 있는 가장 큰 JSON 토큰만큼 커야 합니다.
- 버퍼는 최소한 JSON 내에서 가장 큰 공백 시퀀스만큼 커야 합니다.
- 판독기는 JSON 페이로드의 다음 TokenType을 완전히 읽을 때까지는 읽은 데이터를 추적하지 않습니다. 따라서 버퍼에 바이트가 남아 있으면 판독기에 다시 전달해야 합니다. BytesConsumed를 사용하여 남은 바이트 수를 확인할 수 있습니다.
다음 코드에서는 스트림에서 읽는 방법을 보여 줍니다. 이 예제에서는 MemoryStream을 보여 줍니다. FileStream
이 시작 시 UTF-8 BOM을 포함하는 경우를 제외하고는 FileStream에서 비슷한 코드가 작동합니다. 이 경우 남은 바이트를 Utf8JsonReader
에 전달하기 전에 버퍼에서 3바이트를 제거해야 합니다. 그렇지 않으면 BOM이 JSON의 유효한 부분으로 간주되지 않으므로 판독기가 예외를 throw합니다.
이 샘플 코드는 4KB 버퍼로 시작하며 크기가 작아 전체 JSON 토큰을 수용할 수 없을 때마다 버퍼 크기를 두 배로 늘립니다. 이는 판독기가 JSON 페이로드에 대한 작업을 진행하는 데 필요합니다. 이 코드 조각에 제공된 JSON 샘플은 10바이트와 같이 매우 작은 초기 버퍼 크기를 설정하는 경우에만 버퍼 크기 증가를 트리거합니다. 초기 버퍼 크기를 10으로 설정하는 경우 Console.WriteLine
문은 버퍼 크기 증가의 원인과 영향을 보여 줍니다. 4KB 초기 버퍼 크기에서 전체 샘플 JSON은 각 호출에 Console.WriteLine
의해 표시되며 버퍼 크기를 늘릴 필요가 없습니다.
using System.Text;
using System.Text.Json;
namespace SystemTextJsonSamples
{
public class Utf8ReaderPartialRead
{
public static void Run()
{
var jsonString = @"{
""Date"": ""2019-08-01T00:00:00-07:00"",
""Temperature"": 25,
""TemperatureRanges"": {
""Cold"": { ""High"": 20, ""Low"": -10 },
""Hot"": { ""High"": 60, ""Low"": 20 }
},
""Summary"": ""Hot"",
}";
byte[] bytes = Encoding.UTF8.GetBytes(jsonString);
var stream = new MemoryStream(bytes);
var buffer = new byte[4096];
// Fill the buffer.
// For this snippet, we're assuming the stream is open and has data.
// If it might be closed or empty, check if the return value is 0.
stream.Read(buffer);
// We set isFinalBlock to false since we expect more data in a subsequent read from the stream.
var reader = new Utf8JsonReader(buffer, isFinalBlock: false, state: default);
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
// Search for "Summary" property name
while (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals("Summary"))
{
if (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
}
// Found the "Summary" property name.
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
while (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
// Display value of Summary property, that is, "Hot".
Console.WriteLine($"Got property value: {reader.GetString()}");
}
private static void GetMoreBytesFromStream(
MemoryStream stream, ref byte[] buffer, ref Utf8JsonReader reader)
{
int bytesRead;
if (reader.BytesConsumed < buffer.Length)
{
ReadOnlySpan<byte> leftover = buffer.AsSpan((int)reader.BytesConsumed);
if (leftover.Length == buffer.Length)
{
Array.Resize(ref buffer, buffer.Length * 2);
Console.WriteLine($"Increased buffer size to {buffer.Length}");
}
leftover.CopyTo(buffer);
bytesRead = stream.Read(buffer.AsSpan(leftover.Length));
}
else
{
bytesRead = stream.Read(buffer);
}
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
reader = new Utf8JsonReader(buffer, isFinalBlock: bytesRead == 0, reader.CurrentState);
}
}
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
앞의 예제에서는 버퍼 크기를 늘릴 수 있는 크기 제한을 설정하지 않습니다. 토큰 크기가 너무 클 경우 코드는 OutOfMemoryException 예외와 함께 실패할 수 있습니다. 이 문제는 JSON이 크기가 약 1GB 이상인 토큰을 포함하는 경우 발생할 수 있습니다. 1GB 크기가 두 배가 되면 int32
버퍼에 비해 너무 커지기 때문입니다.
ref 구조체 제한 사항
형식이 Utf8JsonReader
기 ref struct
때문에 특정 제한 사항이 있습니다. 예를 들어 클래스 또는 구조 ref struct
체에 필드로 저장할 수 없습니다.
고성능 Utf8JsonReader
을 달성하려면 입력 ReadOnlySpan<바이트>(그 자체가 ref struct
)를 캐시해야 하기 때문에 a여야 합니다ref struct
. 또한 이 Utf8JsonReader
형식은 상태를 유지하기 때문에 변경 가능합니다. 따라서 값이 아닌 참조로 전달해야 합니다. Utf8JsonReader
by 값을 전달하면 구조체 복사본이 생성되고 상태 변경 내용이 호출자에게 표시되지 않습니다.
ref 구조체를 사용하는 방법에 대한 자세한 내용은 할당 방지를 참조하세요.
UTF-8 텍스트 읽기
Utf8JsonReader
를 사용할 때 가능한 최상의 성능을 얻으려면 UTF-16 문자열이 아닌 UTF-8 텍스트로 이미 인코딩된 JSON 페이로드를 읽으세요. 코드 예제는 Utf8JsonReader를 사용하여 데이터 필터링을 참조하세요.
다중 세그먼트 ReadOnlySequence를 사용하여 읽기
JSON 입력이 ReadOnlySpan<byte>이면 읽기 루프를 진행할 때 판독기의 ValueSpan
속성에서 각 JSON 요소에 액세스할 수 있습니다. 그러나 입력이 ReadOnlySequence<byte>(PipeReader에서 읽은 결과)인 경우에는 일부 JSON 요소가 ReadOnlySequence<byte>
개체의 여러 세그먼트에 걸쳐 있을 수 있습니다. 이러한 요소는 연속 메모리 블록의 ValueSpan에서 액세스할 수 없습니다. 그 대신, 다중 세그먼트 ReadOnlySequence<byte>
가 입력으로 사용될 때마다 판독기에서 HasValueSequence 속성을 폴링하여 현재 JSON 요소에 액세스하는 방법을 확인하세요. 다음은 권장 패턴입니다.
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
여러 JSON 문서 읽기
.NET 9 이상 버전에서는 단일 버퍼 또는 스트림에서 공백으로 구분된 여러 JSON 문서를 읽을 수 있습니다. 기본적으로 Utf8JsonReader
첫 번째 최상위 문서를 추적하는 공백이 아닌 문자를 검색하는 경우 예외를 throw합니다. 그러나 플래그를 사용하여 해당 동작을 JsonReaderOptions.AllowMultipleValues 구성할 수 있습니다.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("null {} 1 \r\n [1,2,3]"u8, options);
reader.Read();
Console.WriteLine(reader.TokenType); // Null
reader.Read();
Console.WriteLine(reader.TokenType); // StartObject
reader.Skip();
reader.Read();
Console.WriteLine(reader.TokenType); // Number
reader.Read();
Console.WriteLine(reader.TokenType); // StartArray
reader.Skip();
Console.WriteLine(reader.Read()); // False
AllowMultipleValues 설정true
되면 잘못된 JSON인 후행 데이터가 포함된 페이로드에서 JSON을 읽을 수도 있습니다.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3] <NotJson/>"u8, options);
reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.
여러 최상위 값을 스트리밍하려면 오버로드 또는 DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken) 오버로드를 DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken) 사용합니다. 기본적으로 DeserializeAsyncEnumerable
단일 최상위 JSON 배열에 포함된 요소를 스트리밍하려고 시도합니다. 매개 변수를 topLevelValues
전달 true
하여 여러 최상위 값을 스트리밍합니다.
ReadOnlySpan<byte> utf8Json = """[0] [0,1] [0,1,1] [0,1,1,2] [0,1,1,2,3]"""u8;
using var stream = new MemoryStream(utf8Json.ToArray());
var items = JsonSerializer.DeserializeAsyncEnumerable<int[]>(stream, topLevelValues: true);
await foreach (int[] item in items)
{
Console.WriteLine(item.Length);
}
/* This snippet produces the following output:
*
* 1
* 2
* 3
* 4
* 5
*/
속성 이름 조회
속성 이름을 조회하려면 바이트 바이트 비교를 호출SequenceEqual하여 수행하지 마세요ValueSpan. 대신, 이 메서드는 JSON에서 이스케이프된 문자를 모두 비우므로 호출 ValueTextEquals합니다. 다음은 “name”이라는 속성을 검색하는 방법을 보여주는 예제입니다.
private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
count++;
}
break;
}
}
Null 값을 null 허용 값 형식으로 읽기
기본 제공 System.Text.Json
API는 null을 허용하지 않는 값 형식만 반환합니다. 예를 들어 Utf8JsonReader.GetBoolean은 bool
을 반환합니다. JSON에서 Null
을 발견하면 예외를 throw합니다. 다음 예제에서는 Null을 처리하는 두 가지 방법을 보여줍니다. 하나는 null 허용 값 형식을 반환하는 방법이고, 다른 하나는 기본값을 반환하는 방법입니다.
public bool? ReadAsNullableBoolean()
{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}
public bool ReadAsBoolean(bool defaultValue)
{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return defaultValue;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}
토큰의 자식 건너뛰기
현재 JSON 토큰의 자식을 건너뛰려고 Utf8JsonReader.Skip() 메서드를 사용합니다. 토큰이 JsonTokenType.PropertyName 형식이면 판독기는 속성 값으로 이동합니다. 다음 코드 조각에서는 판독기를 속성 값으로 이동하는 데 Utf8JsonReader.Skip()을 사용하는 예제를 보여 줍니다.
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Summary = "Hot"
};
byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast);
var reader = new Utf8JsonReader(jsonUtf8Bytes);
int temp;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
{
if (reader.ValueTextEquals("TemperatureCelsius"))
{
reader.Skip();
temp = reader.GetInt32();
Console.WriteLine($"Temperature is {temp} degrees.");
}
continue;
}
default:
continue;
}
}
디코딩된 JSON 문자열 사용
.NET 7부터는 디코딩된 JSON 문자열을 사용하기 위해 Utf8JsonReader.GetString() 대신 Utf8JsonReader.CopyString 메서드를 사용할 수 있습니다. 항상 새 문자열을 할당하는 GetString()과 달리, CopyString은 현재 소유한 버퍼에 이스케이프되지 않은 문자열을 복사할 수 있습니다. 다음 코드 조각은 CopyString을 사용하여 UTF-16 문자열을 사용하는 예제를 보여줍니다.
var reader = new Utf8JsonReader( /* jsonReadOnlySpan */ );
int valueLength = reader.HasValueSequence
? checked((int)reader.ValueSequence.Length)
: reader.ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.AsSpan(0, charsRead);
// Handle the unescaped JSON string.
ParseUnescapedString(source);
ArrayPool<char>.Shared.Return(buffer, clearArray: true);
void ParseUnescapedString(ReadOnlySpan<char> source)
{
// ...
}
관련 API
Utf8JsonReader
인스턴스에서 사용자 지정 형식을 역직렬화하려면 JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions)또는 JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonTypeInfo<TValue>)를 호출합니다. 예를 들어 UTF-8에서 역직렬화를 참조하세요.JsonNode과 그로부터 파생되는 클래스는 변경 가능한 DOM을 만드는 기능을 제공합니다. JsonNode.Parse(Utf8JsonReader, Nullable<JsonNodeOptions>)를 호출하여
Utf8JsonReader
인스턴스를JsonNode
로 변환할 수 있습니다. 다음 코드 조각은 예제를 보여 줍니다.using System.Text.Json; using System.Text.Json.Nodes; namespace Utf8ReaderToJsonNode { public class WeatherForecast { public DateTimeOffset Date { get; set; } public int TemperatureCelsius { get; set; } public string? Summary { get; set; } } public class Program { public static void Main() { var weatherForecast = new WeatherForecast { Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" }; byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast); var utf8Reader = new Utf8JsonReader(jsonUtf8Bytes); JsonNode? node = JsonNode.Parse(ref utf8Reader); Console.WriteLine(node); } } }
JsonDocument는
Utf8JsonReader
를 사용하여 읽기 전용 DOM을 빌드하는 기능을 제공합니다.Utf8JsonReader
인스턴스에서 JsonDocument.ParseValue(Utf8JsonReader) 메서드를 호출하여JsonDocument
를 구문 분석합니다. 페이로드를 구성하는 JSON 요소는 JsonElement 형식을 통해 액세스할 수 있습니다. JsonDocument.ParseValue(Utf8JsonReader)를 사용하는 예제 코드는 RoundtripDataTable.cs 및 유추 형식을 개체 속성으로 역직렬화의 코드 조각을 참조하세요.JsonElement.ParseValue(Utf8JsonReader)를 호출하여
Utf8JsonReader
인스턴스를 특정 JSON 값을 나타내는 JsonElement로 구문 분석할 수도 있습니다.
참고 항목
.NET