How to Get JSON.Net to Serialize Members of a Class Deriving from List<T>

How do I get json.net to serialize members of a class deriving from List T ?

By default, Json.Net will treat any class that implements IEnumerable as an array. You can override this behavior by decorating the class with a [JsonObject] attribute, but then only the object properties will get serialized, as you have seen. The list itself will not get serialized because it is not exposed via a public property (rather, it is exposed via the GetEnumerator() method).

If you want both, you can either do as @Konrad has suggested and provide a public property on your derived class to expose the list, or you can write a custom JsonConverter to serialize the whole thing as you see fit. An example of the latter approach follows.

Assuming that your PagedResult<T> class looks something like this:

class PagedResult<T> : List<T>
{
public int PageSize { get; set; }
public int PageIndex { get; set; }
public int TotalItems { get; set; }
public int TotalPages { get; set; }
}

You can make a converter for it like this:

class PagedResultConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(PagedResult<T>));
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
PagedResult<T> result = (PagedResult<T>)value;
JObject jo = new JObject();
jo.Add("PageSize", result.PageSize);
jo.Add("PageIndex", result.PageIndex);
jo.Add("TotalItems", result.TotalItems);
jo.Add("TotalPages", result.TotalPages);
jo.Add("Items", JArray.FromObject(result.ToArray(), serializer));
jo.WriteTo(writer);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
PagedResult<T> result = new PagedResult<T>();
result.PageSize = (int)jo["PageSize"];
result.PageIndex = (int)jo["PageIndex"];
result.TotalItems = (int)jo["TotalItems"];
result.TotalPages = (int)jo["TotalPages"];
result.AddRange(jo["Items"].ToObject<T[]>(serializer));
return result;
}
}

(Notice also that the [JsonObject] and [JsonProperty] attributes are not required with this approach, because the knowledge of what to serialize is encapsulated into the converter class.)

Here is a demo showing the converter in action:

class Program
{
static void Main(string[] args)
{
PagedResult<string> result = new PagedResult<string> { "foo", "bar", "baz" };
result.PageIndex = 0;
result.PageSize = 10;
result.TotalItems = 3;
result.TotalPages = 1;

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new PagedResultConverter<string>());
settings.Formatting = Formatting.Indented;

string json = JsonConvert.SerializeObject(result, settings);
Console.WriteLine(json);
}
}

Output:

{
"PageSize": 10,
"PageIndex": 0,
"TotalItems": 3,
"TotalPages": 1,
"Items": [
"foo",
"bar",
"baz"
]
}

JSON serialization with class inherited from List T

As far as I know with Newtonsoft all you can do is something like this:

[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class DrivenList : List<int>
{

[JsonProperty]
public string Name { get; set; }

private DrivenList() { }

public DrivenList(string name) { this.Name = name; }
}

But this will add you unwanted (maybe) fields.

Personally I will do composition instead of inheritance:

public class DrivenList
{
public string Name { get; set; }

public List<int> Items { get; set; }

private DrivenList() { }

public DrivenList(string name) { this.Name = name; }
}

Serialize object when the object inherits from list

According to the comments above (thanks!) there are two ways to get a correct result:

  • Implementing a custom JsonConverter (see here)
  • Workarround: Create a property in the class which returns the items (see here)

Anyway, inherit from List<T> is rare to be a good solution (see here)

I've tried it with the workarround:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class A : List<B>
{
[JsonProperty]
public double TestA { get; set; }

[JsonProperty]
public B[] Items
{
get
{
return this.ToArray();
}
set
{
if (value != null)
this.AddRange(value);
}
}
}

public class B
{
public double TestB { get; set; }
}

This works for serialization and deserialization. Important: Items must be an Array of B and no List<B>. Otherwise deserialization doesn't work for Items.

serialization of List of derived objects

From the documentation about javascriptserializer

For .NET Framework 4.7.2 and later versions, use the APIs in the System.Text.Json namespace for serialization and deserialization. For earlier versions of .NET Framework, use Newtonsoft.Json. This type was intended to provide serialization and deserialization functionality for AJAX-enabled applications

I.e. you should probably be using another json library.

If you prefer Json.Net, See Json.net serialize/deserialize derived types?. If you prefer System.Text.Json, see How to serialize properties of derived classes with System.Text.Json

Serialize a list of derived list

You don't want to inherit from List<T>.

Create a list property instead:

public class GroupeActes
{
public List<Acte> Actes { get; set; }

public string NomGroupe { get; set; }

public GroupeActes(string nom, List<Acte> liste)
{
NomGroupe = nom;

Actes.AddRange(acte);
}
}

Lists (and other collection types) get special treatment while serializing. You don't want the collection type's public properties (such as Capacity and Count) in your output, so the property you added through inheritance won't be serialized either.

A collection is serialized like this:

if o is IEnumerable
foreach object s in o
serialize o

So the serializer won't even look at your enumerable's properties.

JSON serialize properties on class inheriting list

Your basic difficulty here is that JSON has two types of container: an object, and an array. From the standard:

  • An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).

  • An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace).

To force a collection's properties to be serialized, mark it with [JsonObject]:

[JsonObject]
public class TestResultListModel : List<TestResultModel>
{
public int TotalTestCases { get { return base.Count; } }

public int TotalSuccessful { get { return base.FindAll(t => t.Successful).Count; } }
}

Of course, if you do this, the items will not be serialized, because a JSON container can have properties, or items -- but not both. If you want both, you will need to add a synthetic array property to hold the items -- which can be private if you want.

[JsonObject] will also cause base class properties such as Capacity to be serialized, which you likely do not want. To suppress base class properties, use MemberSerialization.OptIn. Thus your final class should look something like:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class TestResultListModel : List<TestResultModel>
{
[JsonProperty]
public int TotalTestCases { get { return base.Count; } }

[JsonProperty]
// Using Enumerable.Count() is more memory efficient than List.FindAll()
public int TotalSuccessful { get { return this.Count(t => t.Successful); } }

[JsonProperty]
TestResultModel[] Items
{
get
{
return this.ToArray();
}
set
{
if (value != null)
this.AddRange(value);
}
}
}

This gives JSON that looks like:

{
"TotalTestCases": 4,
"TotalSuccessful": 2,
"Items": [
{
"Successful": false,
"ErrorMessage": "STRING"
},
{
"Successful": true,
"ErrorMessage": "STRING"
},
{
"Successful": false,
"ErrorMessage": "STRING"
},
{
"Successful": true,
"ErrorMessage": "STRING"
}
]
}

It is possibly more work than it's worth, since these properties could be reconstructed easily on the client side. (The question Why not inherit from List? suggests avoiding this sort of design.)

Is it possible to serialize derived properties of list members in System.Text.Json?

this works:

var gameUpdates = new List<object>
{
new ConsoleMessage { MessageContent = "Chimzee lost 10 HP!"},
new HitpointsUpdate { MonsterID = 5, NewHP = 90 }
};

var json = JsonSerializer.Serialize(gameUpdates);
Console.WriteLine(json);

[{"Type":"ConsoleMessage","MessageContent":"Chimzee lost 10 HP!"},{"Type":"HitpointsUpdate","MonsterID":5,"NewHP":90}]

A side-note: Your approach might be dangerous, you have to keep a whitelist of allowed types to Deserialize again or an attacker could inject the creation of his own types...It's much safer to just serialize one type of object in one collection.

What if an attacker injects code like this:

"Type":"System.Drawing.Bitmap","Width":"100000", "Height":"100000"

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