The short answer is yes, that would be appropriate places to start. Here's what I ended up with (for now):
public class MyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
foreach (var pi in type.GetProperties().Where(pi => typeof (Entity).IsAssignableFrom(pi.DeclaringType) && Attribute.IsDefined((MemberInfo) pi, typeof(IdNameMemberAttribute))))
{
properties.Add(CreateReferenceProperty(pi, Reflect.GetProperty<Entity>(e => e.Id)));
properties.Add(CreateReferenceProperty(pi, Reflect.GetProperty<Entity>(e => e.Name)));
}
return properties;
}
private JsonProperty CreateReferenceProperty(PropertyInfo reference, PropertyInfo referenceMember)
{
var jsonProperty = base.CreateProperty(reference, MemberSerialization.OptOut);
jsonProperty.PropertyName += referenceMember.Name;
jsonProperty.ValueProvider = new ReferencedValueProvider(reference, referenceMember);
jsonProperty.Writable = false;
return jsonProperty;
}
}
IdNameMemberAttribute
is just an empty attribute that I use to annotate which reference properties I want to serialize. The important bit is that I do NOT annotate it with anything that Json.NET will recognize and use to generate a JsonProperty. That way I don't end up with a duplicate JsonProperty from my CreateProperties.
Alternatively I could have derived from DataMemberAttribute and looked for, modified and cloned the JsonProperty to represent my Id
and Name
.
For my asp.net web api I then set this MyContractResolver as the ContractResolver of JsonFormatter.SerializerSettings.
So that fixed me up for serialization. For deserialization I have a custom ChangeSet object where I store PropertyInfo and objects. During deserialization I then make sure I keep the Ids, and later resolve those from my data store, in my case using custom ActionFilter with access to the data store session.
Here is the essence of my serialization:
var jsonSource = streamReader.ReadToEnd();
var deserializedObject = JsonConvert.DeserializeObject(jsonSource, type, SerializerSettings);
var changeSet = deserializedObject as PropertyChangeSet;
if (changeSet != null)
{
var jsonChange = JObject.Parse(jsonSource)["Change"].Cast<JProperty>().ToArray();
IDictionary<string, int> references = jsonChange.Where(IsReferenceId).ToDictionary(t => t.Name.Substring(0, t.Name.Length - 2), t => t.Value.ToObject<int>());
changeSet.References = references;
var properties = jsonChange.Where(jp => !IsReferenceId(jp)).Select(t => t.Name).ToList();
changeSet.Primitives = properties;
}
And presto, all the gory details of my clean entities and dynamic serialization are encapsulated, sadly in two places, but it couldn't be helped since I don't want to access my data source from the serializer.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…