I need to serialize many different objects with Json.NET. I really have no control over the objects being provided, so I'm generically serializing and de-serializing with TypeNameHandling.All.
However, some of these objects cannot be de-serialized. Specifically, I am getting some System.Reflection.RuntimePropertyInfo types. I'd like to handle these in a standardized fashion, as I am unaware of the target type at the time of de-serialization. Nor do I care, as long as the output object type is correct.
I've attempted a CustomCreationConverter typed to PropertyInfo that is defined in JsonSerializerSettings. However, even though CanConvert() is returning true, the CustomCreationConverter's ReadJson() is never utilized.
The end result is the same as if I had never used the CustomCreationConverter:
ISerializable type 'System.Reflection.RuntimePropertyInfo' does not have a valid constructor. To correctly implement ISerializable a constructor that takes SerializationInfo and StreamingContext parameters should be present.
I need the CustomCreationConverter to handle the ReadJson so that I can manually search for the PropertyInfo myself.
After more investigation, it appears that the Converters I am adding to the JsonSerializerSettings are not being utilized at all. If I use the DeserializeObject overload that includes the Type and collection of JsonConverter, the Converter will be utilized. I am unsure as to what the Converters supplied to the JsonSerializerSettings are used for, but I would expect they would work as I intend in this situation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace Json
{
class Program
{
static void Main(string[] args)
{
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
Converters = new JsonConverter[] { new PropertyInfoConverter(), },
};
var propertyInfo = typeof(Test).GetProperty("Name");
var serialized = JsonConvert.SerializeObject(propertyInfo, jsonSerializerSettings);
var deserialized = JsonConvert.DeserializeObject(serialized, jsonSerializerSettings);
}
}
public class Test
{
public string Name { get; set; }
}
public class PropertyInfoConverter : CustomCreationConverter<PropertyInfo>
{
public override bool CanConvert(Type objectType)
{
return typeof(PropertyInfo).IsAssignableFrom(objectType);
}
public override PropertyInfo Create(Type objectType)
{
return null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return null; // This is never invoked, but is where I would attempt to find the PropertyInfo via Reflection searching.
}
}
}
Assuming the API you need to conform to must input/output a PropertyInfo
object you can cut out the converter and use a new class to save only the information necessary to recreate the object.
private class PropertyInfoData
{
public string TypeName
{
get;
set;
}
public string PropertyName
{
get;
set;
}
public static PropertyInfoData FromProperty(PropertyInfo p)
{
return new PropertyInfoData()
{
TypeName = p.DeclaringType.AssemblyQualifiedName,
PropertyName = p.Name,
};
}
public PropertyInfo ToProperty()
{
return Type.GetType(this.TypeName).GetProperty(this.PropertyName);
}
}
Then you can de/serialize it like this:
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
//Converters = new JsonConverter[] { new PropertyInfoConverter(), },
};
var propertyInfo = typeof(Test).GetProperty("Name");
var serialized = JsonConvert.SerializeObject(PropertyInfoData.FromProperty(propertyInfo), jsonSerializerSettings);
var deserialized = ((PropertyInfoData)JsonConvert.DeserializeObject(serialized, jsonSerializerSettings)).ToProperty();
Here is a really crude (the read method isn't very robust) example using a converter as well:
public class PropertyInfoConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return typeof(PropertyInfo).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string propertyName = null;
string assemblyName = null;
string typeName = null;
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
string value = reader.Value.ToString();
switch (reader.Value.ToString())
{
case "Name":
if (reader.Read())
{
propertyName = reader.Value.ToString();
}
break;
case "AssemblyName":
if (reader.Read())
{
assemblyName = reader.Value.ToString();
}
break;
case "ClassName":
if (reader.Read())
{
typeName = reader.Value.ToString();
}
break;
}
}
}
return Type.GetType(typeName + ", " + assemblyName).GetProperty(propertyName);
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// When the property "CanWrite" returns false this method is never expected to be called.
throw new NotImplementedException();
}
}
Note, in order to force the deserialize method to use the custom converter you should call the generic version of it like this:
var deserialized = JsonConvert.DeserializeObject<PropertyInfo>(serialized, jsonSerializerSettings);
The same is true in your example which is why you weren't hitting your break point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With