I have a custom collection which I would like to serialise with JSON.NET:
I need it to serialise the child collections within this custom collection.
On deserialisation I need to hook up PropertyChanged event for items within the collection.
If I pass my collection as is, Json sees IEnumerable and serialises the items in the collection ok, but ignores the other collections within.
If I attribute the collection with [JsonObject] it will serialise all the internal collections but not the internal _list;
if I add [JsonProperty] to the internal _list it will serialise all collections.
But since it sets the _list as a property during deserialization The Add Method of my custom collection is not called and as a therefore the propertyChanged events of the items within _list never get hooked up.
I tried hiding the internal _list and wrapping it with a public getter setter, I thought if during deserialization it used the public setter to set the internal _list I could attach to the item events there, but that does not work either.
Is there anything I can do to during deserialization to get the notifyproperty changed events of the items in the internal _list hooked up?
Edit: I tried a converter:
public class TrackableCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TrackableCollectionCollection<ITrackableEntity>);
}
public override object ReadJson(
JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// N.B. null handling is missing
var surrogate = serializer.Deserialize<TrackableCollectionCollection<ITrackableEntity>>(reader);
var trackableCollection = new TrackableCollectionCollection<ITrackableEntity>();
foreach (var el in surrogate)
trackableCollection.Add(el);
foreach (var el in surrogate.NewItems)
trackableCollection.NewItems.Add(el);
foreach (var el in surrogate.ModifiedItems)
trackableCollection.ModifiedItems.Add(el);
foreach (var el in surrogate.DeletedItems)
trackableCollection.DeletedItems.Add(el);
return trackableCollection;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
serializer.Serialize(writer, value);
}
}
Gives error:
{"Message":"An error has occurred.","ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.","ExceptionType":"System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Token PropertyName in state Property would result in an invalid JSON object. Path '[0]'.","ExceptionType":"Newtonsoft.Json.JsonWriterException","StackTrace":" at Newtonsoft.Json.JsonWriter.AutoComplete(JsonToken tokenBeingWritten)\r\n at Newtonsoft.Json.JsonWriter.InternalWritePropertyName(String name)\r\n at Newtonsoft.Json.JsonTextWriter.WritePropertyName(String name, Boolean escape)\r\n at Newtonsoft.Json.Serialization.JsonProperty.WritePropertyName(JsonWriter writer)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)\r\n at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)\r\n at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)\r\n at System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at System.Web.Http.WebHost.HttpControllerHandler.d__1b.MoveNext()"}}
here is the collection as I have it so far.
[Serializable]
[JsonObject]
[JsonConverter(typeof(TrackableCollectionConverter))]
public class TrackableCollectionCollection<T> : IList<T> where T : ITrackableEntity
{
[JsonIgnore]
IList<T> _list = new List<T>();
[JsonProperty]
public IList<T> List
{
get { return _list; }
set
{
_list = value;
foreach(var item in _list)
item.PropertyChanged += item_PropertyChanged;
}
}
[DataMember]
public IList<T> NewItems
{
get { return _newItems; }
}
IList<T> _newItems = new List<T>();
[DataMember]
public IList<T> ModifiedItems
{
get { return _modifiedChildren; }
}
IList<T> _modifiedChildren = new List<T>();
[DataMember]
public IList<T> DeletedItems
{
get { return _deletedItems; }
}
IList<T> _deletedItems = new List<T>();
#region Implementation of IEnumerable
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection<T>
public void Add(T item)
{
if (item.Id.Equals(default(Guid)))
_newItems.Add(item);
else
{
// I thought about doing this but that would screw the EF object generation.
// throw new NotSupportedException("");
}
item.PropertyChanged += item_PropertyChanged;
_list.Add(item);
}
public void Clear()
{
NewItems.Clear();
ModifiedItems.Clear();
foreach(var item in _list)
{
item.PropertyChanged -= item_PropertyChanged;
DeletedItems.Add(item);
}
_list.Clear();
}
public bool Contains(T item)
{
return _list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
if (NewItems.Contains(item))
NewItems.Remove(item);
if (ModifiedItems.Contains(item))
ModifiedItems.Remove(item);
if (!DeletedItems.Contains(item))
DeletedItems.Add(item);
return _list.Remove(item);
}
public int Count
{
get { return _list.Count; }
}
public bool IsReadOnly
{
get { return _list.IsReadOnly; }
}
#endregion
#region Implementation of IList<T>
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
public void Insert(int index, T item)
{
if (item.Id.Equals(default(Guid)))
_newItems.Add(item);
else
{
// I thought about doing this but that would screw the EF object generation.
// throw new NotSupportedException("");
}
item.PropertyChanged += item_PropertyChanged;
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
var item = this[index];
if (NewItems.Contains(item))
NewItems.Remove(item);
if (ModifiedItems.Contains(item))
ModifiedItems.Remove(item);
if (!DeletedItems.Contains(item))
DeletedItems.Add(item);
_list.RemoveAt(index);
}
public T this[int index]
{
get { return _list[index]; }
set { _list[index] = value; }
}
#endregion
void item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (((T)sender).Id.Equals(default(Guid)))
return; // The Item is already in the newItems collection
if (ModifiedItems.Contains((T)sender))
return;
ModifiedItems.Add((T)sender);
}
}