Looks like there's a number of ways to do it, but one thing I did not want to do was to have to modify all of my data objects to be aware of how they should be serialized/deserialized.
One way to do this was to take some examples of DefaultContractResolver's others had done (but still didn't do what I needed to do) and modify them to populate readonly fields.
Here's my class that I'd like to Serialize/Deserialize
public class CannotDeserializeThis
{
private readonly IList<User> _users = new List<User>();
public virtual IEnumerable<User> Users => _users.ToList().AsReadOnly();
public void AddUser(User user)
{
_users.Add(user);
}
}
I could serialize this to:
{"Users":[{"Name":"First Guy"},{"Name":"Second Guy"},{"Name":"Third Guy"}]}
But Deserializing this would leave the Users IEnumerable empty. The only way, I could find, around this was to either remove the '.ToList.AsReadonly' on the Users property or implement a DefaultContractResolver as such:
public class ReadonlyJsonDefaultContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
var hasPrivateSetter = property.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
if (!prop.Writable)
{
var privateField = member.DeclaringType.GetRuntimeFields().FirstOrDefault(x => x.Name.Equals("_" + Char.ToLowerInvariant(prop.PropertyName[0]) + prop.PropertyName.Substring(1)));
if (privateField != null)
{
var originalPropertyName = prop.PropertyName;
prop = base.CreateProperty(privateField, memberSerialization);
prop.Writable = true;
prop.PropertyName = originalPropertyName;
prop.UnderlyingName = originalPropertyName;
prop.Readable = true;
}
}
}
}
return prop;
}
}
The DefaultContractResolver is finding the corresponding private backing field, creating a property out of that, and renaming it to the public readonly property.
This assumes a convention, though. That your backing field starts with an underscore and is a lowercase version of your public property. For most of the code we were working with, this was a safe assumption. (e.g. 'Users' -> '_users', or 'AnotherPropertyName' -> '_anotherPropertyName')
var list = new object[] { .... }; var ser = JsonConvert.SerializeObject(list, Formatting.Indented);
? – Fukien