Call default JsonSerializer in a JsonConverter for certain value type arrays

a 夏天 提交于 2020-04-16 12:58:33

问题


I'm trying to achieve roughly what's described here:

Recursively call JsonSerializer in a JsonConverter

In short; To examine a value being deserialised, then either consume it in my own code, or hand it off the the default deserializer.

The example uses a nifty trick to avoid the same custom code being called recursively:

...
else if (reader.TokenType == JsonToken.StartObject)
    // Use DummyDictionary to fool JsonSerializer into not using this converter recursively
    dictionary = serializer.Deserialize<DummyDictionary>(reader);
else
    dictionary = new Dictionary<TKey, TValue>();
return dictionary;

/// <summary>
/// Dummy to fool JsonSerializer into not using this converter recursively
/// </summary>
private class DummyDictionary : Dictionary<TKey, TValue> { }

The DummyDictionary class acts as a proxy to control flow when Json.Net looks for a new deserializer.

I need to achieve the same for a byte[] instead of a Dictionary. If it's a string, I want to pass it off to the default handler. If an array of int, I'll handle myself.

Unfortunately, I can't implement a

private class DummyByteArray : byte[] { }

as byte is a value type and isn't an inheritable interface.

How can I achieve the control I need without changing every instance of a byte[] in my objects to SomeNoddyByteProxy?


回答1:


Firstly, as an aside, I note that Json.NET supports deserializing both an array of integers and a Base64 string to a byte [] array natively. I.e. the following unit test assertions both just work:

Assert.IsTrue(JsonConvert.DeserializeObject<byte []>("[1, 2]")
              .SequenceEqual(new [] { (byte)1, (byte)2 }));
Assert.IsTrue(JsonConvert.DeserializeObject<byte []>("\"AQI=\"")
              .SequenceEqual(new [] { (byte)1, (byte)2 }));

Demo fiddle #1 here.

That being said, there are a few options given in JSON.Net throws StackOverflowException when using [JsonConvert()] as well as this answer to Newtonsoft Json.NET JsonConverter attribute preserve references issue when deserializing for recursively calling the serializer to get a "default" deserialization:

  1. If you don't need to pre-load the JSON into a JToken hierarchy, you can have the converter disable itself using a thread static member, and then call serializer.Deserialize() recursively.

  2. If you do need to pre-load the JSON into a JToken hierarchy, you can embed the hierarchy inside a parent container and supersede and disable the converter using a dummy converter on the container's member.

An example converter using option #1 might look as follows:

public sealed class ByteConverter : JsonConverter<byte[]>
{
    [ThreadStatic]
    static bool disabled;

    // Disables the converter in a thread-safe manner.
    bool Disabled { get { return disabled; } set { disabled = value; } }

    public override bool CanRead { get { return !Disabled; } }

    public override byte[] ReadJson(JsonReader reader, Type objectType, byte[] existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        switch (reader.MoveToContentAndAssert().TokenType) // Skip past comments
        {
            case JsonToken.Null:
                return null;

            case JsonToken.StartArray:
                // Your custom logic here, e.g.:
                return serializer.Deserialize<List<byte>>(reader).ToArray();

            default:
                using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
                    return serializer.Deserialize<byte []>(reader);
        }
    }

    // Remainder omitted
    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, byte[] value, JsonSerializer serializer) => throw new NotImplementedException();
}

public struct PushValue<T> : IDisposable
{
    Action<T> setValue;
    T oldValue;

    public PushValue(T value, Func<T> getValue, Action<T> setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }

    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        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)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

Demo fiddle #2 here.

However, in your case things are simpler. Json.NET considers a byte [] array represented as a Base64 string to be a primitive, so you can simply load it into a JToken and use the JToken Explicit Conversion (JToken toByte[]) operator to cast it to a byte[] array like so:

public class ByteConverter : JsonConverter<byte[]>
{
    public override byte[] ReadJson(JsonReader reader, Type objectType, byte[] existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        switch (reader.MoveToContentAndAssert().TokenType) // Skip past comments
        {
            case JsonToken.Null:
                return null;

            case JsonToken.StartArray:
                // Your custom logic here, e.g.:
                return serializer.Deserialize<List<byte>>(reader).ToArray();

            default:
                return (byte[])JToken.Load(reader);
        }
    }

    // Remainder omitted

This entirely avoids use of the serializer. Demo fiddle #3 here.



来源:https://stackoverflow.com/questions/60254867/call-default-jsonserializer-in-a-jsonconverter-for-certain-value-type-arrays

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!