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