Json.NET - 使用 TypeNameHandling 时处理未知派生类型
Json.NET - handle unknown derived types when using TypeNameHandling
上下文
我有一个 Web API returns 使用 Json.NET 序列化为 JSON 的派生类型列表(都具有相同的基类型)。然后,客户端代码将 JSON 解析回派生类型列表。这是通过 API 客户端完成的,该客户端引用了 Web API 使用的相同 class 对象。客户端和服务器都在使用 Json.NET 的 TypeNameHandling.Objects 功能。
问题
当一个新的派生类型被添加到 Web API 时,这会导致客户端反序列化抛出异常,因为客户端没有对新派生类型的引用 class .
目标
当类型对客户端未知时,反序列化器应默认为派生的特定“未知”class,而不是抛出异常。
约束条件
我不想引入每次添加新的派生类型时都需要更改代码的反序列化逻辑,即对类型的 switch
语句。我想继续使用 Json.NET 的内置类型处理功能,但使用“默认为未知”逻辑。
代码
下面是突出问题的示例代码:
public abstract class Animal { }
public class Unknown : Animal { }
public class Dog : Animal { }
public class Cat : Animal { }
public class Dummy : Animal { } // to be swapped out of the JSON with a "new" derived type
var animals = new List<Animal>()
{
new Dog(),
new Cat(),
new Dummy()
};
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
};
var json = JsonConvert.SerializeObject(animals, settings);
// simulate unknown derived type in JSON
json = json.Replace("Dummy", "NewAnimal");
// throws serialization exception
var deserializedAnimals = JsonConvert.DeserializeObject<List<Animal>>(json, settings);
// goal: deserializedAnimals should contain 3 objects of type { Dog, Cat, Unknown }
这是似乎可以解决问题的习惯 JsonConverter
:
public class DefaultToUnknownConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Animal).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
JObject jObject = JObject.Load(reader);
try
{
// attempt to deserialize to known type
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
{
// create new serializer, as opposed to using the serializer parm, to avoid infinite recursion
JsonSerializer tempSerializer = new JsonSerializer()
{
TypeNameHandling = TypeNameHandling.Objects
};
return tempSerializer.Deserialize<Animal>(jObjectReader);
}
}
catch (JsonSerializationException)
{
// default to Unknown type when deserialization fails
return jObject.ToObject<Unknown>();
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
// create reader and copy over settings
JsonReader jTokenReader = jToken.CreateReader();
jTokenReader.Culture = reader.Culture;
jTokenReader.DateFormatString = reader.DateFormatString;
jTokenReader.DateParseHandling = reader.DateParseHandling;
jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jTokenReader.FloatParseHandling = reader.FloatParseHandling;
jTokenReader.MaxDepth = reader.MaxDepth;
jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
return jTokenReader;
}
将转换器添加到解串器设置:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = { new DefaultToUnknownConverter() }
};
DefaultToUnknownConverter
class 可以通过传入 json 设置、使用泛型等变得更通用。
实现自定义JsonConverter参考:
上下文
我有一个 Web API returns 使用 Json.NET 序列化为 JSON 的派生类型列表(都具有相同的基类型)。然后,客户端代码将 JSON 解析回派生类型列表。这是通过 API 客户端完成的,该客户端引用了 Web API 使用的相同 class 对象。客户端和服务器都在使用 Json.NET 的 TypeNameHandling.Objects 功能。
问题
当一个新的派生类型被添加到 Web API 时,这会导致客户端反序列化抛出异常,因为客户端没有对新派生类型的引用 class .
目标
当类型对客户端未知时,反序列化器应默认为派生的特定“未知”class,而不是抛出异常。
约束条件
我不想引入每次添加新的派生类型时都需要更改代码的反序列化逻辑,即对类型的 switch
语句。我想继续使用 Json.NET 的内置类型处理功能,但使用“默认为未知”逻辑。
代码
下面是突出问题的示例代码:
public abstract class Animal { }
public class Unknown : Animal { }
public class Dog : Animal { }
public class Cat : Animal { }
public class Dummy : Animal { } // to be swapped out of the JSON with a "new" derived type
var animals = new List<Animal>()
{
new Dog(),
new Cat(),
new Dummy()
};
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
};
var json = JsonConvert.SerializeObject(animals, settings);
// simulate unknown derived type in JSON
json = json.Replace("Dummy", "NewAnimal");
// throws serialization exception
var deserializedAnimals = JsonConvert.DeserializeObject<List<Animal>>(json, settings);
// goal: deserializedAnimals should contain 3 objects of type { Dog, Cat, Unknown }
这是似乎可以解决问题的习惯 JsonConverter
:
public class DefaultToUnknownConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Animal).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
JObject jObject = JObject.Load(reader);
try
{
// attempt to deserialize to known type
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
{
// create new serializer, as opposed to using the serializer parm, to avoid infinite recursion
JsonSerializer tempSerializer = new JsonSerializer()
{
TypeNameHandling = TypeNameHandling.Objects
};
return tempSerializer.Deserialize<Animal>(jObjectReader);
}
}
catch (JsonSerializationException)
{
// default to Unknown type when deserialization fails
return jObject.ToObject<Unknown>();
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
// create reader and copy over settings
JsonReader jTokenReader = jToken.CreateReader();
jTokenReader.Culture = reader.Culture;
jTokenReader.DateFormatString = reader.DateFormatString;
jTokenReader.DateParseHandling = reader.DateParseHandling;
jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jTokenReader.FloatParseHandling = reader.FloatParseHandling;
jTokenReader.MaxDepth = reader.MaxDepth;
jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
return jTokenReader;
}
将转换器添加到解串器设置:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = { new DefaultToUnknownConverter() }
};
DefaultToUnknownConverter
class 可以通过传入 json 设置、使用泛型等变得更通用。
实现自定义JsonConverter参考: