问题
We're using Web API with Json.Net using TypeNameHandling = TypeNameHandling.Objects
in our serializer settings. This works fine, but we use the type information only client-side, never for deserialization. Our serialized objects look like this:
{
"$type": "PROJECTNAME.Api.Models.Directory.DtoName, PROJECTNAME.Api",
"id": 67,
"offices": [{
"$type": "PROJECTNAME.Api.Models.Directory.AnotherDtoName, PROJECTNAME.Api",
"officeName": "FOO"
}]
},
I would like to customize the value in the $type
property so it reads as:
{
"$type": "Models.Directory.DtoName",
"id": 67,
"offices": [{
"$type": "Models.Directory.AnotherDtoName",
"officeName": "FOO"
}]
},
I already have a contract resolver that inherits from CamelCasePropertyNamesContractResolver
. I figure what I need to do is turn off TypeNameHandling
and add a custom property myself. I'm 95% there:
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var assemblyName = type.Assembly.GetName().Name;
var typeName = type.FullName.Substring(assemblyName.Length + 1);
var typeProperty = new JsonProperty()
{
PropertyName = "$type",
PropertyType = typeof(string),
Readable = true,
Writable = true,
ValueProvider = null // ????? typeName
};
var retval = base.CreateProperties(type, memberSerialization);
retval.Add(typeProperty);
return retval;
}
At this point I'm stuck with supplying the property's value.
I'm unsure that this is the correct approach because each of the ValueProvider
types from Json.Net take a MemberInfo
as a constructor parameter. I don't have a MemberInfo
to supply as a parameter, so.... I'm stuck.
How do I add a custom $type
value? Since I'm not doing deserialization in C# I will never need to convert the type information back into a type.
回答1:
Rather than adding a synthetic $type
property, you should create a custom ISerializationBinder and override ISerializationBinder.BindToName. This method is called during serialization to specify the type information to emit when TypeNameHandling is enabled.
For instance, the following strips the assembly information as well as the PROJECTNAME.Api.
portion of the namespace:
public class MySerializationBinder : ISerializationBinder
{
const string namespaceToRemove = "PROJECTNAME.Api.";
readonly ISerializationBinder binder;
public MySerializationBinder() : this(new Newtonsoft.Json.Serialization.DefaultSerializationBinder()) { }
public MySerializationBinder(ISerializationBinder binder)
{
if (binder == null)
throw new ArgumentNullException();
this.binder = binder;
}
#region ISerializationBinder Members
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
binder.BindToName(serializedType, out assemblyName, out typeName);
if (typeName != null && typeName.StartsWith(namespaceToRemove))
typeName = typeName.Substring(namespaceToRemove.Length);
assemblyName = null;
}
public Type BindToType(string assemblyName, string typeName)
{
throw new NotImplementedException();
}
#endregion
}
Then you can serialize your DtoName
object with it as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
SerializationBinder = new MySerializationBinder(),
TypeNameHandling = TypeNameHandling.Objects,
};
var json = JsonConvert.SerializeObject(dto, Formatting.Indented, settings);
Notes:
Newtonsoft introduced ISerializationBinder in release 10.0.1 as a replacement to System.Runtime.Serialization.SerializationBinder, apparently because that type is missing in some versions of .Net core. If you are using a version of Json.NET that precedes 10.0.1 you will need to create a custom version of that instead.
Note also that SerializationBinder.BindToName() was introduced in .Net 4.0, so if you are using an old version of Json.NET and an old version of .Net itself then this solution will not work.
As you are not doing deserialization in c# I simply threw an exception from BindToType(). But if someone were to implement
BindToType()
, they should take heed of the caution from TypeNameHandling caution in Newtonsoft Json and be sure to sanitize the incoming types to prevent construction of harmful types.
来源:https://stackoverflow.com/questions/49283251/custom-type-value-for-serialized-objects