Json.net serialization of custom collection implementing IEnumerable<T> - c#

I have a collection class that implements IEnumerable and I am having trouble deserializing a serialized version of the same. I am using Json.net v 4.0.2.13623
Here is a simplier version of my collection class that illustrates my issue
public class MyType
{
public int Number { get; private set; }
public MyType(int number)
{
this.Number = number;
}
}
public class MyTypes : IEnumerable<MyType>
{
private readonly Dictionary<int, MyType> c_index;
public MyTypes(IEnumerable<MyType> seed)
{
this.c_index = seed.ToDictionary(item => item.Number);
}
public IEnumerator<MyType> GetEnumerator()
{
return this.c_index.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public int CustomMethod()
{
return 1; // Just for illustration
}
}
When I serialize it i get the following json
[
{
"Number": 1
},
{
"Number": 2
}
]
But then when i try to deserialize i get the following exception
System.Exception: Cannot create and populate list type MyTypes
Have also tried using serialization hints but have had no success
These are the test function i have used
public void RoundTrip()
{
var _myTypes1 = new MyTypes(new[] { new MyType(1), new MyType(2) });
var _jsonContent = JsonConvert.SerializeObject(_myTypes1, Formatting.Indented);
var _myTypes2 = JsonConvert.DeserializeObject<MyTypes>(_jsonContent);
}
public void RoundTripWithSettings()
{
var _myTypes1 = new MyTypes(new[] { new MyType(1), new MyType(2) });
var _serializationSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Arrays };
var _jsonContent = JsonConvert.SerializeObject(_myTypes1, Formatting.Indented, _serializationSettings);
var _myTypes2 = JsonConvert.DeserializeObject<MyTypes>(_jsonContent, _serializationSettings);
}
Has anybody managed to serialize their own collection objects ?
Thanks in advance
Pat

As of Json.NET 6.0 Release 3 and later (I tested on 8.0.3), this works automatically as long as the custom class implementing IEnumerable<MyType> has a constructor that takes an IEnumerable<MyType> input argument. From the release notes:
To all future creators of immutable .NET collections: If your collection of T has a constructor that takes IEnumerable then Json.NET will automatically work when deserializing to your collection, otherwise you're all out of luck.
Since the MyTypes class in the question has just such a constructor, this now just works. Demonstration fiddle: https://dotnetfiddle.net/LRJHbd.
Absent such a constructor, one would need to add a parameterless constructor and implement ICollection<MyType> (instead of just IEnumerable<MyType>) with a working Add(MyType item) method. Json.NET can then construct and populate the collection. Or deserialize to an intermediate collection as in the original answer.

I believe JSON .NET depends on having a parameterless constructor for the types it deserializes to. From a deserialization point of view it has no idea what to provide to the constrcutor.
As for your collection, again how is it supposed to know how to populate an implementation of IEnumerable<T> which only defines how to enumerate the collection not how to populate it. You should instead deserialize directly to some standard IEnumerable<MyType> (or directly to IEnumerable<MyType> which I believe JSON.NET supports) and then pass it to your constructor.
I believe all of the following should work in this case:
var _myTypes2 = new MyTypes(JsonConvert.DeserializeObject<MyTypes[]>(_jsonContent));
var _myTypes2 = new MyTypes(JsonConvert.DeserializeObject<List<MyTypes>>(_jsonContent));
var _myTypes2 = new MyTypes(JsonConvert.DeserializeObject<IEnumerable<MyTypes>>(_jsonContent));
Alternatively, though more work, if you need to deserialized directly you can implement something like IList on your collection which should allow JSON.NET to use the IList methods to populate your collection.

Related

RedisSessionStateProvider with ProtoBuf serialization causing errors

I am trying to get protobuf serialization working with the RedisSessionStateProvider. I have specified the redisSerializerType as a custom class which implements Microsoft.Web.Redis.ISerializer - here is the deserialization code:
public object Deserialize(byte[] data)
{
return DeserializeDirect(data);
}
private object DeserializeDirect(byte[] data)
{
using (var memoryStream = new MemoryStream(data))
{
return Serializer.Deserialize<object>(memoryStream);
}
return null;
}
As I need to implement Microsoft.Web.Redis.ISerializer the signature for deserialize uses a return type of object and there is no way to pass in the actual type being returned. So when DeserializeDirect tries to use the Protobuf.Serializer to deserialize it (as expected) says "Type is not expected, and no contract can be inferred: System.Object". I am using a web app with .NET framework 4.6.1 and I was hoping somebody could point out what I am doing wrong.
Thanks!
Normally, protobuf-net really wants to know the exact type. You can, however, cheat using DynamicType. This tells protobuf-net to include additional type metadata - something it doesn't usually include.
Note that this can make you code brittle - it may fail if the type changes in you code!
I will be implementing Any soon (as part of 2.3.0), which is another option here.
public static void Main()
{
// the actual object we care about
object obj = new Foo { X = 1 };
// serialize and deserialize via stub
var stub = new Stub { Data = obj };
var clone = Serializer.DeepClone(stub);
// prove it worked
Console.WriteLine(clone.Data);
// prove it is a different instance
Console.WriteLine(ReferenceEquals(obj, clone.Data));
}
[ProtoContract]
public class Foo
{
[ProtoMember(1)]
public int X { get; set; }
public override string ToString() => $"X={X}";
}
[ProtoContract]
public sealed class Stub
{
[ProtoMember(1, DynamicType = true)]
public object Data { get; set; }
}

How to prevent _t and _v when inserting into MongoDB?

I'm utilizing Dictionary. After .insert() there are "_t" and "_v". Two posts here talked about serialization converting to JSON first then BSON. I'm using MongoDB's driver v2.4.3,
mCollection.InsertOne(x);
IMongoCollection<myDoc> mCollection = Db.GetCollection<myDoc>("whatever");
If I do JSON-to-BSON, it complains about can't convert BsonDocument to myDoc. Switching to IMongoCollection<BsonDocument> mCollection = Db.GetCollection<BsonDocument>("whatever"); still get _t and _v.
How to avoid _t and _v?
Here is my code of data type and utilization:
public class myObjForDictionary
{
//...
}
public class myDoc
{
// ... some other elements, then Dictionary
public Dictionary<string, object> myDictionary { get; set; }
}
// to instantiate the
class myClass
{
// define MongoDB connection, etc.
// instantiate myDoc and populate data
var x = new myDoc
{
//...
myDictionary = new Dictionary<string, object>
{
{ "type", "something" },
{ "Vendor", new object[0] },
{ "obj1", //data for myObjForDictionary
}
};
}
}
I think you're looking for the DictionarySerializationOption... which gives you a couple of different options out of the box to determine how your dictionary gets serialized.
You'll need to save some information (_t) of what object the deserializer needs to create when deserializing the object, if not it won't know what to create from the BSON.
Alternatively, you could change the Dictionary<string, object> to Dictionary<string, BsonDocument> and deal with the BsonDocuments directly within your code. This will be very similar to how you use JObject within Newtonsoft.Json.
It also happens when the passed model to mongo is not exactly the type that is defined on the model.
In this case, mongo will add _t as it identifies that you expect to read the inheritor's class type, and not the type that is defined on the model.
For example:
public class ParentClass { }
public class ChildClass : ParentClass { }
public class ModelToInsert
{
public ParentClass property { get; set; }
}
...
// ChildClass is passed instead of ParentClass
collection.InsertOne(new ModelToInsert { property = new ChildClass() });

How to write a generic method to built a list of data with common interface?

We are going to use compressed json string for storing / passing the configuration data in our system.
We have added two methods fromJson and toJson in the interface (IData) of all data class. And we have built a generic method to convert a list of such data to json as below:
public static dynamic List2Json(List<IData> data)
{
List<dynamic> json = new List<dynamic>();
foreach(IData o in data)
{
json.Add(o.toJson());
}
return json;
}
But there has problem to build similar method to convert the json array back to the list. We cannot create an object for a interface. (i.e. IData x = new IData();) Or even we create a base class, we still cannot do it in this way.
public void Json2List(ref List<IData> list, DynamicJsonArray json)
{
list = new List<IData>();
foreach(dynamic o in json)
{
IData x = new IData();
x.fromJson(o);
list.Add(x);
}
}
We also tried the Generic Methods as below, but still we cannot create the object by T x = new T();
public void Json2List<T>(ref List<T> data, dynamic json) where T : IData
{
data = new List<T>();
foreach(dynamic o in json)
{
T x = new T();
x.fromJson(o);
data.Add(o);
}
}
We also think about the other way to return the new instance by a static method of the data class. But unfortunately, static method is not supported in interface, and static method in base class cannot be override. So we still need a instance of that class in order to create another instance. Finally, we make a stupid way that require a instance of required class as parameter, and use this dummy instance to generate all instance of the list.
public interface IData
{
IData createFromJson(dynamic json);
dynamic toJson();
}
public abstract class baseData : IData
{
public virtual IData objectFromJson(dynamic json, baseData source)
{
return source.createFromJson(json);
}
public abstract IData createFromJson(dynamic json);
}
public static void Json2List<T>(ref List<T> data, dynamic json, baseData source) where T : IData
{
data = new List<T>();
foreach(dynamic o in json)
{
data.Add(source.objectFromJson(o, source));
}
}
public class a : baseData
{
public override IData createFromJson(dynamic json)
{
a object = new a();
// assign the value to object from json here
return object;
}
}
// in the program need to convert from json
//
dynamic json = ... // json object containing required data
List<a> list = new List<a>();
Json2List(ref list, json, new a());
Now it works, but it need to create a dummy object in calling the method.
i.e. Json2List(ref list, json, new a()); in above example. It's really dummy and we only need this instance for calling the createFromJson method.
May I know if there has any way to improve the program, so that there has no need to build a dummy object in this way?
This should be possible with a combination of generics and the new() clause in where. The latter tells the compiler to allow new on generic type. You have to make sure that generic type has a constructor without parameters.
public void Json2List<T>(...) where T : IData, new()
{
data = new T() as IData;
...
}

Use JSON.net Binder/TypeNameHandling for nested types

Using JSON.net, I want to deserialize a list of types that inherit from an abstract class. I have used KnownTypesBinder (source) like
var jsonSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.TypeNameHandling = TypeNameHandling.Auto;
jsonSettings.Binder = new KnownTypesBinder { KnownTypes = new List<Type> { ... } };
Now this works perfectly fine in the WEB API modelbinder; KnownTypesBinder.BindToType is being called an the object can be deserialized. In a different part of the web application I have to deserialize a JSON string, so I would like to re-use the same JsonFormatter. According to the docs, the following should work;
JsonConvert.DeserializeObject<T>(String, JsonSerializerSettings);
However when I do just that:
JsonConvert.DeserializeObject<A>(jsonString, GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings);
The following error is thrown:
JsonSerializationException. Could not create an instance of type C. Type is an interface or abstract class and cannot be instantiated.
My types look something like this;
class A {
public B B { get; set; }
}
class B {
public List<C> C { get; set; }
}
abstract class C { }
class C1: C { }
class C2: C { }
Also creating a new JsonSerializerSettings and setting Binder and TypeNameHandling makes no difference. I found that KnownTypesBinder.BindToType is not being called at all. Is there something in JSON.net that I'm missing?
Feeling stupid here. The discriminator used by JSON.net is called "$type". As I was using curl to send the POST payload, bash tried to resolve $type to an environment variable. As there was no such variable, the final JSON was simply {"": "C"} instead of {"$type": "C"}.

How to pass parameter to constructor deserializing json

I have a small problem with passing some parent instance to a constructor when deserializing an object with Newtonsoft.Json.
Let's assume i have the following classes
public class A
{
public string Str1 { get; set; }
public IList<B> Bs { get; set; }
}
public class B
{
public B(A a)
{
// a should not be null!
Console.WriteLine(a.Str)
}
}
And now i serailze and than deserialize the object a like this:
A a = new A()
a.Bs = new List<B>()
a.Bs.Add(new B(a));
a.Bs.Add(new B(a));
a.Bs.Add(new B(a));
var json = JsonConvert.SerializeObject(a);
// Here i need to call the constructor of B when creating new instances
var newA = JsonConvert.DeserializeObject<A>(json);
The problem is, that when deserializing the object, null will be passed to the constructor of B. Does any one has solved this issue/problem before?
Thank you very much!
In your question and comments you've said that the class B does not have any public property for A. So, when you serialize B, then no A will be written to the JSON, because Json.Net only serializes the public information by default. Therefore, when deserializing, there will not be enough information to recreate B, because there is no A in the JSON. So, step one is making B's reference to A visible to Json.Net. If you don't want to make it public, that is fine, but you will at least need to mark the member with a [JsonProperty] attribute to allow Json.Net to "see" it.
public class B
{
[JsonProperty]
private A a;
public B(A a)
{
this.a = a; // be sure to set the A member in your constructor
}
}
Now if you do the above you will run into a second problem: your class structure has a reference loop (A has a list of Bs which each refer back to A), and the serializer will throw an exception by default in this case. The solution is to set the serializer's PreserveReferencesHandling setting to Objects (the default is None). This will not only allow the serializer to handle the reference loops during serialization, but will also preserve the original references during deserialization, so that all the Bs will refer to the same A instance. (This is accomplished via special $id and $ref properties that are written into the JSON.)
JsonSerializerSettings settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
};
var json = JsonConvert.SerializeObject(a, settings);
var newA = JsonConvert.DeserializeObject<A>(json, settings);
Working example: https://dotnetfiddle.net/N0FUID
What I like to do it I have to pass objects in a constructor is to create the object first using my default constructor and then call populate object to set all properties that are not skipped as I decorated the properties with [JsonIgore]
var settings = new JsonSerializerSettings()
{
Error = HandleJsonDeserializationError,
PreserveReferencesHandling = PreserveReferencesHandling.Objects
}
var myObject = new ComplexObject(param1,param2);
JsonConvert.PopulateObject(json, myObject, settings);
You can continue populating objects and deal with any issues if you handle serialisation errors in the JsonSettings property. The signature is as follows:
static void HandleJsonDeserializationError(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs errorArgs)
{
var currentError = errorArgs.ErrorContext.Error.Message;
errorArgs.ErrorContext.Handled = true;
//loging framework logs the error, set brake point etc when debug.
Logger.Log(currentError, LogLevel.Exceptions);
}

Categories

Resources