强制使用 Utf8JsonReader.ValueSequence 进行测试

问题描述 投票:0回答:1

我编写了自己的 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。 但是,我已经使用像

BigInteger.Pow(new BigInteger(long.MaxValue), 1234);
这样大的 BigIntegers 进行了测试,但仍然无法使用 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);
}

问候 迈克尔

c# json biginteger system.text.json jsonconverter
1个回答
0
投票

Utf8JsonReader
有一个构造函数,它需要一个
ReadOnlySequence<byte>
,所以你可以将你的JSON字符串编码为UTF8字节数组,将其分成小块,然后将该块序列转换为
ReadOnlySequence<byte>
使用
ReadOnlySequenceFactory
这个答案使用system.text.json从分块字符串数组中反序列化非常大的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);
}

演示小提琴在这里

© www.soinside.com 2019 - 2024. All rights reserved.