How to Serialize/Deserialize a Custom Collection with Additional Properties Using JSON.Net

How to serialize/deserialize a custom collection with additional properties using Json.Net

The problem is the following: when an object implements IEnumerable, JSON.net identifies it as an array of values and serializes it following the array Json syntax (that does not include properties),
e.g. :

 [ {"FooProperty" : 123}, {"FooProperty" : 456}, {"FooProperty" : 789}]

If you want to serialize it keeping the properties, you need to handle the serialization of that object by hand by defining a custom JsonConverter :

// intermediate class that can be serialized by JSON.net
// and contains the same data as FooCollection
class FooCollectionSurrogate
{
// the collection of foo elements
public List<Foo> Collection { get; set; }
// the properties of FooCollection to serialize
public string Bar { get; set; }
}

public class FooCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(FooCollection);
}

public override object ReadJson(
JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// N.B. null handling is missing
var surrogate = serializer.Deserialize<FooCollectionSurrogate>(reader);
var fooElements = surrogate.Collection;
var fooColl = new FooCollection { Bar = surrogate.Bar };
foreach (var el in fooElements)
fooColl.Add(el);
return fooColl;
}

public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
// N.B. null handling is missing
var fooColl = (FooCollection)value;
// create the surrogate and serialize it instead
// of the collection itself
var surrogate = new FooCollectionSurrogate()
{
Collection = fooColl.ToList(),
Bar = fooColl.Bar
};
serializer.Serialize(writer, surrogate);
}
}

Then use it as follows:

var ss = JsonConvert.SerializeObject(collection, new FooCollectionConverter());

var obj = JsonConvert.DeserializeObject<FooCollection>(ss, new FooCollectionConverter());

Serialise custom collection With additional properties and on Add to hookup item propertChanged events

You could serialize your custom container as a JsonObject, as you are doing now, and serialize the embedded list as a proxy ObservableCollection<T>. You can then listen in for additions and removals to the proxy and handle them accordingly. Note -- no custom JsonConverter required. Since I don't have your definition for ITrackableEntity, here's a quick prototype wrapper IList<T> for a List<T>:

[Serializable]
[JsonObject]
public class ListContainer<T> : IList<T>
{
[JsonIgnore]
readonly List<T> _list = new List<T>();

[JsonProperty("List")]
private IList<T> SerializableList
{
get
{
var proxy = new ObservableCollection<T>(_list);
proxy.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(proxy_CollectionChanged);
return proxy;
}
set
{
_list.Clear();
_list.AddRange(value);
}
}

void proxy_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems.Cast<T>())
Add(item);
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.NewItems.Cast<T>())
Remove(item);
}
else
{
Debug.Assert(false);
throw new NotImplementedException();
}
}

[JsonIgnore]
public int Count
{
get { return _list.Count; }
}

[JsonIgnore]
public bool IsReadOnly
{
get { return ((IList<T>)_list).IsReadOnly; }
}

// Everything beyond here is boilerplate.

#region IList<T> Members

public int IndexOf(T item)
{
return _list.IndexOf(item);
}

public void Insert(int index, T item)
{
_list.Insert(index, item);
}

public void RemoveAt(int index)
{
_list.RemoveAt(index);
}

public T this[int index]
{
get
{
return _list[index];
}
set
{
_list[index] = value;
}
}

#endregion

#region ICollection<T> Members

public void Add(T item)
{
_list.Add(item);
}

public void Clear()
{
_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)
{
return _list.Remove(item);
}

#endregion

#region IEnumerable<T> Members

public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}

#endregion

#region IEnumerable Members

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

#endregion
}

And then, to test:

    public static void TestListContainerJson()
{
var list = new ListContainer<int>();
list.Add(101);
list.Add(102);
list.Add(103);

var json = JsonConvert.SerializeObject(list);
var newList = JsonConvert.DeserializeObject<ListContainer<int>>(json);
Debug.Assert(list.SequenceEqual(newList)); // No assert.
}

Update

It turns out that Json.NET follows the same pattern as XmlSerializer: if you serialize the proxy list as an array, the setter will be called with the fully populated array after being read, and you can add them as required:

[Serializable]
[JsonObject]
public class ListContainer<T> : IList<T>
{
[JsonIgnore]
readonly List<T> _list = new List<T>();

[JsonProperty("List")]
private T [] SerializableList
{
get
{
return _list.ToArray();
}
set
{
Clear();
foreach (var item in value)
Add(item);
}
}

[JsonIgnore]
public int Count
{
get { return _list.Count; }
}

[JsonIgnore]
public bool IsReadOnly
{
get { return ((IList<T>)_list).IsReadOnly; }
}

// Everything beyond here is boilerplate.
}

This is much cleaner than my first solution.

Also, I suspect that your NewItems and ModifiedItems list contain references to items in the main _list. By default Json.NET will effectively clone these during serialization & deserialization. To avoid this, look into the PreserveReferencesHandling functionality. More here.

How can I make Json.NET serialize and deserialize declared properties of custom dynamic types that also implement IDictionary string, object ?

You have a few problems here:

  1. You need to correctly override DynamicObject.GetDynamicMemberNames() as explained in this answer to Serialize instance of a class deriving from DynamicObject class by AlbertK
    for Json.NET to be able to serialize your dynamic properties.

    (This has already been fixed in the edited version of your question.)

  2. Declared properties do not show up unless you explicitly mark them with [JsonProperty] (as explained in this answer to C# How to serialize (JSON, XML) normal properties on a class that inherits from DynamicObject) but your type definitions are read-only and cannot be modified.

    The problem here seems to be that JsonSerializerInternalWriter.SerializeDynamic() only serializes declared properties for which JsonProperty.HasMemberAttribute == true. (I don't know why this check is made there, it would seem to make more sense to set CanRead or Ignored inside the contract resolver.)

  3. You would like for your class to implement IDictionary<string, object>, but if you do, it breaks deserialization; declared properties are no longer populated, but are instead added to the dictionary.

    The problem here seems to be that DefaultContractResolver.CreateContract() returns JsonDictionaryContract rather than JsonDynamicContract when the incoming type implements IDictionary<TKey, TValue> for any TKey and TValue.

Assuming you have fixed issue #1, issues #2 and #3 can be handled by using a custom contract resolver such as the following:

public class MyContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
// Prefer JsonDynamicContract for MyDynamicObject
if (typeof(MyDynamicObject).IsAssignableFrom(objectType))
{
return CreateDynamicContract(objectType);
}
return base.CreateContract(objectType);
}

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
// If object type is a subclass of MyDynamicObject and the property is declared
// in a subclass of MyDynamicObject, assume it is marked with JsonProperty
// (unless it is explicitly ignored). By checking IsSubclassOf we ensure that
// "bookkeeping" properties like Count, Keys and Values are not serialized.
if (type.IsSubclassOf(typeof(MyDynamicObject)) && memberSerialization == MemberSerialization.OptOut)
{
foreach (var property in properties)
{
if (!property.Ignored && property.DeclaringType.IsSubclassOf(typeof(MyDynamicObject)))
{
property.HasMemberAttribute = true;
}
}
}
return properties;
}
}

Then, to use the contract resolver, cache it somewhere for performance:

static IContractResolver resolver = new MyContractResolver();

And then do:

var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
string json = JsonConvert.SerializeObject(obj, settings);

Sample fiddle here.

How to serialize/deserialize an ArrayList and properties that are of an Object type

I decided to use soap serialization instead as it adds the types into the xml

  using System;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Soap;

namespace Utils
{
public class XMLUtil
{
public static Byte[] StringToUTF8ByteArray(String xmlString)
{
return new UTF8Encoding().GetBytes(xmlString);
}

public static String SerializeToXML<T>(T objectToSerialize)
{
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
soapFormatter.Serialize(ms, objectToSerialize);
String decoded = Encoding.UTF8.GetString(ms.ToArray());
return decoded;
}
}

public static T DeserializeFromXML<T>(string xmlString) where T : class
{
T retval = default(T);
using (MemoryStream stream = new MemoryStream(StringToUTF8ByteArray(xmlString)))
{
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
retval = soapFormatter.Deserialize(stream) as T;
}
return retval;
}
}
}

Newtonsoft Jsonconverter not serializing properties when derived of list

Don't derive you class from List<> as mentioned here.

Change your class to:

public class Plep 
{
public string Name { get; set; } = "smew";
public List<Subs> Subs {get;set;}

public void SaveToFile(string file)
{

using (StreamWriter wrt = new StreamWriter(file))
{
wrt.WriteLine(JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
//TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,

}));
}
}


Related Topics



Leave a reply



Submit