Я написал свой собственный BigIntegerConverter для сериализации/десериализации JSON (.Net System.Text.Json). В методе Read я проверил, используется ли ValueSequence.
...
string stringValue;
if (reader.HasValueSequence)
{
stringValue = Encoding.UTF8.GetString(reader.ValueSequence);
}
else
{
stringValue = Encoding.UTF8.GetString(reader.ValueSpan);
}
if (BigInteger.TryParse(stringValue, CultureInfo.InvariantCulture, out var result))
{
return result;
}
...
Теперь я хочу протестировать этот код, но пока могу добраться только до дерева else. Основываясь на документации, я предположил, что ValueSequence будет использоваться, если данные станут достаточно большими.
Однако я уже тестирую BigIntegers размером с BigInteger.Pow(new BigInteger(long.MaxValue), 1234);
и до сих пор не могу использовать ValueSequence.
Я что-то пропустил? Есть ли способ обеспечить использование ValueSqeuence в целях тестирования?
Мой тестовый пример выглядит так
[Theory]
[MemberData(nameof(GetNotNullTestData))]
public void Read_EntityWithNotNullableBigInteger(string name, BigInteger expected, string value)
{
// Arrange
var json = $$"""{"Name":"{{name}}","NotNullableValue":{{value}}}""";
// Act
var result = JsonSerializer.Deserialize<NotNullableBigIntegerEntity>(json, _options);
// Assert
Assert.NotNull(result);
Assert.Equal(name, result.Name);
Assert.Equal(expected, result.NotNullableValue);
}
С уважением Майкл
Вероятно, зависит от способа буферизации базового потока. Как вы вводите JSON?
@Charlieface Я отредактировал вопрос и добавил метод тестирования.
При десериализации строки, вероятно, всегда будет использоваться один диапазон, почему бы и нет? Теперь попробуйте это с FileStream
.
Utf8JsonReader имеет конструктор , который принимает ReadOnlySequence<byte> , поэтому вы можете взять строку JSON, закодировать ее в массив байтов UTF8, разбить ее на небольшие фрагменты, а затем преобразовать эту последовательность фрагментов в a ReadOnlySequence<byte>
с помощью ReadOnlySequenceFactory
из этого ответа в Десериализовать очень большой json из фрагментированного массива строк с помощью system.text.json.
Сначала представим следующий фабричный класс:
// From this answer https://stackoverflow.com/a/61087772 to https://stackoverflow.com/questions/61079767/deserialize-very-large-json-from-a-chunked-array-of-strings-using-system-text-js
public static class ReadOnlySequenceFactory
{
public static ReadOnlySequence<T> AsSequence<T>(this IEnumerable<T []> buffers) => ReadOnlyMemorySegment<T>.Create(buffers.Select(a => new ReadOnlyMemory<T>(a)));
public static ReadOnlySequence<T> AsSequence<T>(this IEnumerable<ReadOnlyMemory<T>> buffers) => ReadOnlyMemorySegment<T>.Create(buffers);
// There is no public concrete implementation of ReadOnlySequenceSegment<T> so we must create one ourselves.
// This is modeled on https://github.com/dotnet/runtime/blob/v5.0.18/src/libraries/System.Text.Json/tests/BufferFactory.cs
// by https://github.com/ahsonkhan
class ReadOnlyMemorySegment<T> : ReadOnlySequenceSegment<T>
{
public static ReadOnlySequence<T> Create(IEnumerable<ReadOnlyMemory<T>> buffers)
{
ReadOnlyMemorySegment<T>? first = null;
ReadOnlyMemorySegment<T>? current = null;
foreach (var buffer in buffers)
{
var next = new ReadOnlyMemorySegment<T> { Memory = buffer };
if (first == null)
first = next;
else
{
current!.Next = next;
next.RunningIndex = current.RunningIndex + current.Memory.Length;
}
current = next;
}
if (first == null)
first = current = new ();
return new ReadOnlySequence<T>(first, 0, current!, current!.Memory.Length);
}
}
}
Затем напишите свой конвертер, например. следующее:
public class BigIntegerConverter : JsonConverter<BigInteger>
{
// The actual implementation seems to be in INumberBase<TSelf>.TryParse() so I had to do this to call the method:
static bool TryParse<TSelf>(ReadOnlySpan<byte> utf8Text, out TSelf? value) where TSelf : IUtf8SpanParsable<TSelf>, new() =>
TSelf.TryParse(utf8Text, NumberFormatInfo.InvariantInfo, out value);
public override BigInteger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.Number)
throw new JsonException(string.Format("Found token {0} but expected token {1}", reader.TokenType, JsonTokenType.Number ));
var utf8Text = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
if (TryParse<BigInteger>(utf8Text, out var value))
return value;
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, BigInteger value, JsonSerializerOptions options) =>
writer.WriteRawValue(value.ToString(NumberFormatInfo.InvariantInfo), false);
}
Теперь вы сможете написать свой тестовый метод следующим образом, разбивая входной JSON на небольшие фрагменты:
public record NotNullableBigIntegerEntity(string Name, BigInteger NotNullableValue);
JsonSerializerOptions _options = new()
{
Converters = { new BigIntegerConverter() },
};
public void Read_EntityWithNotNullableBigInteger(string name, BigInteger expected, string value)
{
int byteChunkSize = 3;
// Arrange
var json = $$"""{"Name":"{{name}}","NotNullableValue":{{value}}}""";
// Break into chunks
var utf8json = Encoding.UTF8.GetBytes(json);
var sequence = utf8json.Chunk(byteChunkSize).AsSequence();
// Act
var reader = new Utf8JsonReader(sequence);
var result = JsonSerializer.Deserialize<NotNullableBigIntegerEntity>(ref reader, _options);
// Assert
Assert.NotNull(result);
Assert.Equal(name, result?.Name);
Assert.Equal(expected, result?.NotNullableValue);
}
Примечания:
BigInteger реализует IUtf8SpanParsable<BigInteger>, который позволяет осуществлять прямой анализ диапазонов байтов в кодировке UTF8. Таким образом, нет необходимости вызывать Encoding.UTF8.GetString()
для создания строки UTF16, соответствующей текущему значению.
Основываясь на документации, я предположил, что ValueSequence будет использоваться, если данные станут достаточно большими. -- Может быть, а может быть, и нет. MSFT, похоже, намеревался использовать ReadOnlySequence<byte>
при асинхронной десериализации из некоторого потока запросов или ответов, но вы выполняете синхронную десериализацию из строки в памяти. Решение MSFT разбить эту строку на один диапазон байтов UTF8 или на несколько последовательностей байтов UTF8 — это деталь реализации, которую они не разглашают.
(Когда я проверяю источник ссылки, кажется, что текущий код преобразует входящую строку в однобайтовый диапазон.)
Демо-рабочий пример здесь.
Я должен разобраться в этом. Но первая попытка просто скопировать и вставить ваше решение даже не вызывает метод .Read класса Converter.
@IamanSwtrse - добавьте минимально воспроизводимый пример . Я изменил BigIntegerConverter().Read()
, добавив Console.WriteLine()
, и он действительно вызывается, см. dotnetfiddle.net/Kre0Qy.
Нашел его через 2 часа. Я нашел опечатку в названии свойства... :( Теперь все работает как часы. Спасибо.
Из документации «Для входных данных в ReadOnlySpan<byte> это всегда возвращает false. Для входных данных в ReadOnlySequence<byte> это возвращает true только в том случае, если значение токена охватывает более одного сегмента и, следовательно, не может быть представлено. как пролет». Я не совсем понимаю, что они имеют в виду под значениями токенов, но кто-то, вероятно, знает.