json.net -как добавить свойство $type только на корневом объекте
Я хочу изменить свой json.NET сериализатор для добавления свойства $type только к объектам, реализующим данный интерфейс, но не к любому свойству или вложенным объектам.
С TypeNameHandling.Auto (по умолчанию)
{
"PropertyA": 123,
"PropertyB": "foo",
"PropertyC": [1, 2, 3, 4]
}
С TypeNameHandling.Все!--8-->
{
"$type": "JsonNetTypeNameHandling.TestEvent, jsonNetTypeNameHandling",
"PropertyA": 123,
"PropertyB": "foo",
"PropertyC": {
"$type": "System.Collections.Generic.List`1[[System.Int32, mscorlib]], mscorlib",
"$values": [1, 2, 3, 4 ]
}
}
что я хочу!--8-->
{
"$type": "JsonNetTypeNameHandling.TestEvent, jsonNetTypeNameHandling",
"PropertyA": 123,
"PropertyB": "foo",
"PropertyC": [1, 2, 3, 4]
}
я экспериментирую с пользовательским ContractResolver, но я не могу заставить его работать:
class Program
{
static void Main(string[] args)
{
var serializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ContractResolver = new EnableTypeNameHandlingAllOnlyForEvents(),
Formatting = Formatting.Indented
};
var event1 = new TestEvent() { PropertyA = 123, PropertyB = "foo", PropertyC = new List<int> { 1, 2, 3, 4 } };
string event1Serialized = JsonConvert.SerializeObject(event1, serializerSettings);
Console.WriteLine(event1Serialized);
Console.ReadLine();
}
}
public interface IEvent
{
}
public class TestEvent : IEvent
{
public int PropertyA { get; set; }
public string PropertyB { get; set; }
public List<int> PropertyC { get; set; }
}
public class EnableTypeNameHandlingAllOnlyForEvents : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var x = base.CreateObjectContract(objectType);
if (typeof(IEvent).IsAssignableFrom(x.UnderlyingType))
{
// What to do to tell json.NET to add $type to instances of this (IEvent) type???
}
return x;
}
}
1 ответов
если вы желаете "$type" свойство на корневом объекте и нормально с его появлением на вложенных полиморфных объектах и массивах при необходимости используйте следующую перегрузку вместе с TypeNameHandling.Auto: JsonConvert.SerializeObject(Object, Type, JsonSerializerSettings).
С docs:
public static string SerializeObject( Object value, Type type, JsonSerializerSettings settings )тип система типов.Тип Тип сериализуемого значения. Этот параметр используется, когда TypeNameHandling автоматически записывает имя типа, если тип значения не совпадает. Указание типа является необязательным.
т. е., do:
var serializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
var event1Serialized = JsonConvert.SerializeObject(event1, typeof(IEvent), serializerSettings);
если вы желаете "$type" на корневом объекте и будет не примите его на вложенных полиморфных объектах и массивах, даже если в противном случае требуется, вам нужно будет использовать TypeNameHandling.All вместе с распознаватель пользовательских контрактов устанавливает, что JsonContainerContract.ItemTypeNameHandling = TypeNameHandling.None:
public class SuppressItemTypeNameContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static SuppressItemTypeNameContractResolver instance;
// Using a static constructor enables fairly lazy initialization. http://csharpindepth.com/Articles/General/Singleton.aspx
static SuppressItemTypeNameContractResolver() { instance = new SuppressItemTypeNameContractResolver(); }
public static SuppressItemTypeNameContractResolver Instance { get { return instance; } }
protected SuppressItemTypeNameContractResolver() : base() { }
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
var containerContract = contract as JsonContainerContract;
if (containerContract != null)
{
if (containerContract.ItemTypeNameHandling == null)
containerContract.ItemTypeNameHandling = TypeNameHandling.None;
}
return contract;
}
}
затем использовать его например:
var serializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
ContractResolver = SuppressItemTypeNameContractResolver.Instance,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
var event1Serialized = JsonConvert.SerializeObject(event1, serializerSettings);
наконец, обратите внимание на это предостережение от Newtonsoft docs:
TypeNameHandling следует использовать с осторожностью, когда приложение десериализует JSON из внешнего источника. Входящие типы должны проверяться с помощью настраиваемого SerializationBinder при десериализации со значением, отличным от None.
для обсуждения, почему это может быть необходимо, см. typenamehandling внимание в Newtonsoft JSON-Формате, как настроить Json.NET для создания уязвимого веб-API, и черная шляпа Альваро Муньоса и Александра Мирошаhttps://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf