问题
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:
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 callserializer.Deserialize()
recursively.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