Вызовите JsonSerializer по умолчанию в JsonConverter для определенных массивов типов значений - PullRequest
1 голос
/ 17 февраля 2020

Я пытаюсь примерно достичь того, что описано здесь:

Рекурсивно вызывать JsonSerializer в JsonConverter

Короче говоря; Чтобы исследовать десериализованное значение, затем либо использовать его в своем собственном коде, либо передать его десериализатору по умолчанию.

В этом примере используется хитрый трюк, чтобы избежать рекурсивного вызова того же пользовательского кода:

...
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> { }

Класс DummyDictionary действует как прокси для управления потоком, когда Json. Net ищет новый десериализатор.

Мне нужно добиться того же для byte[] вместо словаря. Если это строка, я хочу передать ее обработчику по умолчанию. Если массив int, я сам справлюсь.

К сожалению, я не могу реализовать

private class DummyByteArray : byte[] { }

, поскольку байт является типом значения и не наследуемым интерфейсом.

Как мне добиться необходимого контроля, не меняя каждый экземпляр byte[] в моих объектах на SomeNoddyByteProxy?

1 Ответ

1 голос
/ 17 февраля 2020

Во-первых, отметим, что Json. NET поддерживает десериализацию как массива целых чисел, так и строки Base64 в массив byte []. Т.е. следующие утверждения модульного теста оба просто работают :

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 здесь .

При этом, существует несколько опций, указанных в JSON. Net, создает исключение StackOverflowException при использовании [JsonConvert ()] , а также этот ответ до Newtonsoft Json. NET Атрибут JsonConverter сохраняет проблему с ссылками при десериализации для рекурсивного вызова сериализатора для получения десериализации «по умолчанию»:

  1. Если вы не Если вам необходимо предварительно загрузить JSON в иерархию JToken, вы можете отключить сам преобразователь с помощью элемента потока stati c, а затем вызвать serializer.Deserialize() рекурсивно.

  2. Если вам нужно предварительно загрузить JSON в иерархию JToken, вы можете встроить иерархию в родительский контейнер и заменить и отключить преобразователь с помощью фиктивного преобразователя. на элементе контейнера.

Пример конвертера с использованием опции # 1 может выглядеть следующим образом:

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;
    }
}

Демо-скрипта # 2 здесь .

Однако в вашем случае все проще. Json. NET считает массив byte [], представленный в виде строки Base64, примитивом, поэтому вы можете просто загрузить его в JToken и использовать явное преобразование JToken (JToken toByte []) * Оператор 1046 * для приведения его к массиву byte[] примерно так:

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

Это полностью исключает использование сериализатора. Демонстрационная скрипка № 3 здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...