如何在C#中使用JsonConvert.DeserializeObject反序列化system.memory

zzoitvuj  于 2023-07-01  发布在  C#
关注(0)|答案(2)|浏览(300)

我有一个使用System.Memory<double>属性的类,我们称之为PROP 1和CLASS 1
当这个类被序列化为JSON文件时,它以一种非常常见的方式保存:
(...)“PROP1”:[7200.0,7200.0,7200.0](...)
尝试通过JsonConvert.DeserializeObject<CLASS1>File.ReadAllText(fileName));进行反序列化时
我得到以下异常:
无法反序列化当前JSON数组(例如[1,2,3])转换为类型'System.Memory' 1[System.Double]',因为该类型需要JSON对象(例如,{“name”:“value”})来正确反序列化。要修复此错误,请将JSON更改为JSON对象(例如{“name”:“value”})或将反序列化的类型更改为数组或实现集合接口的类型(例如ICollection,IList),比如可以从JSON数组反序列化的List。也可以将JsonArrayAttribute添加到类型中,以强制它从JSON数组反序列化。
我猜它不能序列化它,因为内存的工作方式更像一个指针,所以你应该先创建一个内存引用的对象。但是我找不到解决这个问题的方法(除了将类重写为另一种类型)……我也找不到任何有类似问题的线程。有什么想法如何反序列化它??

bvuwiixz

bvuwiixz1#

由于Json.NET似乎没有内置的序列化和反序列化Memory<T>ReadOnlyMemory<T>的支持,您可以为它们创建generic converters,将序列化和反序列化Memory<T>ReadOnlyMemory<T>切片内容的“快照”,如下所示:

public class MemoryConverter<T> : JsonConverter<Memory<T>>
{
    public override void WriteJson(JsonWriter writer, Memory<T> value, JsonSerializer serializer) =>
        serializer.SerializeMemory((ReadOnlyMemory<T>)value, writer, false);
        
    public override Memory<T> ReadJson(JsonReader reader, Type objectType, Memory<T> existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        reader.MoveToContentAndAssert().TokenType switch
        {
            JsonToken.String when typeof(T) == typeof(char) => 
                ((T [])(object)serializer.Deserialize<string>(reader).ToCharArray()).AsMemory(),
            JsonToken.StartArray when typeof(T) == typeof(byte) => 
                ((T [])(object)serializer.Deserialize<List<byte>>(reader).ToArray()).AsMemory(),
            _ => 
                serializer.Deserialize<T []>(reader).AsMemory()
        };
}

public class ReadOnlyMemoryConverter<T> : JsonConverter<ReadOnlyMemory<T>>
{
    public override void WriteJson(JsonWriter writer, ReadOnlyMemory<T> value, JsonSerializer serializer) =>
        serializer.SerializeMemory((ReadOnlyMemory<T>)value, writer, serializeAsString : true);
        
    public override ReadOnlyMemory<T> ReadJson(JsonReader reader, Type objectType, ReadOnlyMemory<T> existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        reader.MoveToContentAndAssert().TokenType switch
        {
            JsonToken.String when typeof(T) == typeof(char) => 
                (ReadOnlyMemory<T>)(object)serializer.Deserialize<string>(reader).AsMemory(),
            JsonToken.StartArray when typeof(T) == typeof(byte) => 
                ((T [])(object)serializer.Deserialize<List<byte>>(reader).ToArray()).AsMemory(),
            _ => 
                serializer.Deserialize<T []>(reader).AsMemory()
        };
}

public static partial class JsonExtensions
{
    internal static void SerializeMemory<T>(this JsonSerializer serializer, ReadOnlyMemory<T> value, JsonWriter writer, bool serializeAsString) 
    {
        switch (value)
        {
            case ReadOnlyMemory<byte> m when MemoryMarshal.TryGetArray(m, out var seg) && seg.Offset == 0 && seg.Count == seg.Array.Length:
                writer.WriteValue(seg.Array); // Base64 encoded array.
            break;
            case ReadOnlyMemory<byte> m:
                writer.WriteValue(m.ToArray()); // Base64 encoded slice.
            break;
            case ReadOnlyMemory<char> m when serializeAsString:
                writer.WriteValue(m.ToString());
            break;
            default:
                serializer.Serialize(writer, MemoryMarshal.ToEnumerable(value));
            break;
        }
    }

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        ArgumentNullException.ThrowIfNull(reader);
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        ArgumentNullException.ThrowIfNull(reader);
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

然后使用以下设置序列化和反序列化模型:

var settings = new JsonSerializerSettings
{
    Converters = { new MemoryConverter<double>(), new ReadOnlyMemoryConverter<double>() },
};

var json = JsonConvert.SerializeObject(class1, settings);
var model2 = JsonConvert.DeserializeObject<CLASS1>(json, settings);

和/或应用于您的模型,如下所示:

public class CLASS1
{
    [JsonConverter(typeof(MemoryConverter<double>))]
    public Memory<double> PROP1 { get; set; }
};

警告和注意事项:

***警告:数组切片的引用不会被保留。**如果您的Memory<double>是某个数组的切片,并且该数组也在模型中的其他地方被序列化,那么当反序列化Memory<double>时,它将不会通过引用引用反序列化的数组,而是引用它自己的数组副本。

如果需要保留数组切片的引用,则需要一个不同的(并且复杂得多的)转换器。

  • 由于字节数组被Json.NET(和System.Text.Json)序列化为Base64字符串,所以我对Memory<byte>ReadOnlyMemory<byte>也做了同样的操作。但是如果Memory<byte>之前已经被序列化为JSON数组,它将被正确读取。
  • 由于ReadOnlyMemory<char>有时可以 Package 一个字符串,所以我将其序列化,但没有对只能 Package 一个char数组的Memory<char>进行相同的操作。

如果你不想这样,可以在ReadOnlyMemoryConverter<T>.Read()内部传递serializeAsString : false

  • 没有转换器,我无法使用Json.NET 13.0.3为Memory<T>生成合理的序列化。我得到的不是JSON数组,而是
{"PROP1":{"Length":6,"IsEmpty":false}}

也许你已经安装了一些转换器来处理序列化而不是反序列化?
演示小提琴here

s1ag04yj

s1ag04yj2#

以下答案适用于JSON.Net。

您可以使用自定义JsonConverter

public class MemoryConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType) =>
        typeof(Memory<T>).IsAssignableFrom(objectType);

    public override bool CanRead => true;

    public override bool CanWrite => true;

    public override object ReadJson(JsonReader reader, 
                                    Type objectType, 
                                     object existingValue, 
                                     JsonSerializer serializer)
    {
        var mem = new Memory<T>(serializer.Deserialize<T[]>(reader));
        if (existingValue is Memory<T> existingMem && !existingMem.IsEmpty)
        {
            mem.CopyTo(existingMem);
            return existingMem;
        }
        return mem;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (!(value is Memory<T> mem))
            throw new ArgumentException($"invalid type {value?.GetType().FullName}");
        writer.WriteStartArray();
        var span = mem.Span;
        for (var item in span)
        {
            serializer.Serialize(writer, item);
        }
        writer.WriteEndArray();
    }
}

然后你就可以应用它了

[JsonConverter(typeof(MemoryConverter<int>))]
public Memory<int> YourValue { get; set; }

dotnetfiddle

相关问题