Generic JSON deserialization in grid - c#

Hi I'm using JSON for de/serialization items in my grid control (copy/paste feature). I want to use JSON because the copy paste has to work across application domain.
The value type of each cell can be different (simple and complex types).
class CellItem<TValue>
{
public TValue Value { get; set; }
public int GridColumnIndex { get; set; }
public int GridRowIndex { get; set; }
}
In order to do the serialization I need to get the cell position and store it's value. So I created a serialization type
public class MatrixItem
{
public int ColumnIndex { get; set; }
public int RowIndex { get; set; }
public object Value { get; set; }
}
and put them in a list to serialize them into the clipboard. That works quite fine.
Now when I do deserialization I want to tell JSON which type to deserialize. How can I do that? I know the type information is not stored in the serialization string so I have to take care of that in code.
I somehow need to ask the CellItem which type to use for deserialization and let the converter know. But I'm not sure how to do that.
There is something like [JsonConverter(typeof(*))] but that needs a compile time known converter and the converter would change during runtime.

Maybe this could help you:
Let's say you got an interface:
interface Fruit
{
bool IsSweet();
}
You then got several classes representing your data:
public class Apple : Fruit
{
public bool IsSweet()
{
return false;
}
}
public class Banana : Fruit
{
public bool IsSweet()
{
return true;
}
}
Now when it comes to serialization you would like to store the type, so when u deserialize you know if it was a Banana or an Apple.
You can do so using JsonSerializerSettings:
List<Fruit> fruits = new List<Fruit>();
fruits.Add(new Banana());
fruits.Add(new Apple());
JsonSerializerSettings settings = new JsonSerializerSettings();
//This is the IMPORTANT part
settings.TypeNameHandling = TypeNameHandling.All;
string json = JsonConvert.SerializeObject(fruits, settings);
The Json will look like this:
{
"$type": "System.Collections.Generic.List`1[[YourNamespace.Fruit, YourNamespace]], mscorlib",
"$values": [
{
"$type": "YourNamespace.Banana, YourNamespace"
},
{
"$type": "YourNamespace.Apple, YourNamespace"
}
]
}
Now when u deserialize you want get the derived type, not the interface:
Simply pass the settings used above again:
var deserializedFruites = (List<Fruit>)JsonConvert.DeserializeObject(json, settings);
Count = 2
[0]: {YourNamespace.Banana}
[1]: {YourNamespace.Apple}
Now to adapt to your specific problem I guess it would be the easiest if you just export the type to json too.

Related

How to use JSON.NET to serialize a list of POCO's as a list of strings, to reduce size?

I've got a simple POCO like this:
public class Edit { ... }
public class CharInsert : Edit
{
public int ParagraphIndex { get; set; }
public int CharacterIndex { get; set; }
public char Character { get; set; }
}
which serializes in JSON like this (note that I'm recording the object type, because of the inheritance):
{
"$type": "MyNamespace.CharInsert, MyAssembly",
"paragraphIndex": 7,
"characterIndex": 15,
"character": "e"
}
But this takes up a HUGE amount of space for a fairly little amount of data. And I have a LOT of them, so I need to be more compact about it.
I made a custom JsonConverter so that it will instead serialize as this:
"CharInsert|7|15|e"
and when I persist a list of these, I get:
[
"CharInsert|7|12|Z",
"CharInsert|7|13|w",
"CharInsert|7|14|i",
"CharInsert|7|15|e",
]
But when I try to deserialize this list, I get the error:
'Error converting value "CharInsert|7|12|Z" to type 'MyNamespace.Edit'
I suppose this is because the actual object is a subclass of the Edit type and it doesn't know which one because it doesn't know how to parse the string. How can I implement this so it can parse the string, resolve the typename contained therein, and then create the needed object type?
An alternative approach to custom converters, consider using [JsonProperty(PropertyName = "")] to shorten the json property names, which should decrease space and you dont have to worry about custom converters.
public class CharInsert : Edit
{
[JsonProperty(PropertyName = "p")]
public int ParagraphIndex { get; set; }
[JsonProperty(PropertyName = "i")]
public int CharacterIndex { get; set; }
[JsonProperty(PropertyName = "c")]
public char Character { get; set; }
}
I figured it out. The issue is that, without type information in the serialized string ("CharInsert|7|15|e"), the deserializer doesn't know what derived class to call.
So I made a JsonConverter for the base Edit type that knows how to parse the string and create and return and object from that string:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//get the persisted value
var s = reader.Value?.ToString();
var fields = s.Split('|');
var typeName = ...get the type name from the field
var type = Type.GetType(typeName);
//create an object from the remaining fields using the ctor(string value) that
//each subclass must have
return System.Activator.CreateInstance(type, new object[] { fields.Skip(1).ToJoinedString("|") });
}

Converter that uses generics to parse json model

I have a converter class that receives json in input, here are 2 valid examples:
{
"method": "Model",
"payload": {
"key": "value"
}
}
and
{
"method": "OtherModel",
"payload": {
"foo": "bar"
}
}
In C#, I have classes mapped to each possible model:
public class Model
{
public string Key { get; set; }
}
public class OtherModel
{
public string Foo { get; set; }
}
I need a generic converter
How can I use the string value in the method of the JSON to convert in a generic way the content of the payload field?
Is using a huge switch the only way? This is the prototype I have so far but there are hundreds of different models so it will grow quite large...
public IResult ParseJson(string json)
{
Regex regexMessageName = new Regex("\"messageName\": \"(.*?)\"", RegexOptions.Compiled);
var messageName = regexMessageName.Match(json).Groups[1].Value;
switch (messageName)
{
case "Model":
var raw = JsonConvert.DeserializeObject<JsonData<Model>>(json);
return new LogInfoRequestResult<Model> { Raw = raw };
case "OtherModel":
var raw = JsonConvert.DeserializeObject<JsonData<OtherModel>>(json);
return new LogInfoRequestResult<OtherModel> { Raw = raw };
}
}
If you want complete control of your classes, and allow them to evolve independently, then you can have one base class that owns the Method, and then as many subclasses as you want with their own definition of the payload.
First, parse into the baseclass, just to get a strongly typed deserialization of Method
Then, there are a lot of patterns to address branching logic.
If you have 1-2 cases, an if statement is fine
If you have 3-5 cases, you can use a switch
If you have 6-10 cases, you can create a dictionary that maps method name to class type
If you have more than that, you can use the strategy pattern and pass an interface around
Here's an example of how you could write the code:
var json = #"{
'method': 'Model',
'payload': {
'key': 'value'
}
}";
var modelBase = JsonConvert.DeserializeObject<ModelBase>(json);
var methodMapping = new Dictionary<string, Type>()
{
{MethodTypes.Model.ToString(), typeof(Model)},
{MethodTypes.OtherModel.ToString(), typeof(OtherModel)},
};
Type methodClass = methodMapping[modelBase.Method];
var result = JsonConvert.DeserializeObject(json, methodClass);
Note: Since we're programmatically determining the correct type, it's hard to pass to a generic <T>, so this uses the overload of DeserializeObject that takes type as a param
And here are the classes that model incoming messages
public enum MethodTypes
{
Model,
OtherModel
}
public class ModelBase
{
public string Method { get; set; }
}
public class Model : ModelBase
{
public ModelInfo Payload { get; set; }
public class ModelInfo
{
public string Key { get; set; }
}
}
public class OtherModel : ModelBase
{
public ModelInfo Payload { get; set; }
public class ModelInfo
{
public string Foo { get; set; }
}
}
Dictionary<string,string>
If your data is always going to be "foo":"bar" or "key":"value" .... string:string, then Cid's suggesting to use Dictionary<string,string> Payload makes a lot of sense. Then figure out however you want to map from that c# class in a c# constructor that returns whatever type you want.
Additional Resources:
How to handle both a single item and an array for the same property using JSON.net
Deserializing polymorphic json classes without type information using json.net
JSON.NET - Conditional Type Deserialization
Conditionally deserialize JSON string or array property to C# object using JSON.NET?
You can instanciate an object of the expected class using Activator.CreateInstance(), then populate it with JsonConvert.PopulateObject()
In example :
Type t = Type.GetType($"NameSpaceName.{messageName}"); // this must be a fully qualified name
object obj = Activator.CreateInstance(t);
JsonConvert.PopulateObject(json, obj);

How to serialize/deserialize list with interface

I've trying to serialize and deserialize a list with an interface, the problem is that yamldotnet cannot deserialize it.
I've show it to you with an example:
interface IAnimal
{
string Name { get; }
}
class Cat : IAnimal
{
public string Name { get; set; }
public string CustomThing { get; set; } = "1a";
}
class Dog : IAnimal
{
public string Name { get; set; }
public bool IsSomething { get; set; } = true;
}
When I now try to serialize this:
var serializer = new Serializer();
List<IAnimal> animals = new List<IAnimal>()
{
new Cat() { Name = "Oscar" },
new Dog() { Name = "WuffWuff" }
};
var writer = File.CreateText("test.yml");
serializer.Serialize(writer, animals);
writer.Close();
The result of this would be
- Name: Oscar
CustomThing: 1a
- Name: WuffWuff
IsSomething: true
I understand that as this point yamldotnet cannot know which types that are, and it is needed that the class types are also definied inside the yml
So how can I archive this?
I've already tried to find something in the documentation but there are only examples and nothing with interfaces / list's.
You can specify the type of a node using tags:
- !cat
Name: Oscar
CustomThing: 1a
- !dog
Name: WuffWuff
IsSomething: true
You will need to tell YamlDotNet what types correspond to !cat and !dog:
deserializer.RegisterTagMapping("tag:yaml.org,2002:cat", typeof(Cat));
deserializer.RegisterTagMapping("tag:yaml.org,2002:dog", typeof(Dog));
Note: ! is a shorthand for tag:yaml.org,2002:. When registering the tag mapping, we need to use the full Uri.
Here's an example code very similar to yours: https://dotnetfiddle.net/GZtqvL
The serializer also supports emitting tags. To activate this behavior, you need to specify the SerializationOptions.Roundtrip flag in the constructor. At the moment it is not possible to specify tag mappings on the serializer, though.

Serialize subclass with Json.NET

I am trying to use Json.NET to serialize a subclass. The resulting json contains the serialized properties for the superclass but not the properties on the subclass object.
This seems to be related to an issue I found here on SO. But having to write a JsonConverter seems like overkill.
Sample subclass:
public class MySubclass : List<string>
{
public string Name { get; set; }
}
Sample of the serialization:
MySubclass myType = new MySubclass() { Name = "Awesome Subclass" };
myType.Add("I am an item in the list");
string json = JsonConvert.SerializeObject(myType, Newtonsoft.Json.Formatting.Indented);
Resulting json:
[
"I am an item in the list"
]
I expected to result to be more like this:
{
"Name": "Awesome Subclass",
"Items": [
"I am an item in the list"
]
}
Perhaps I am just not using the right configuration when serializing. Anyone have any suggestions?
According the documentation:
.NET lists (types that inherit from IEnumerable) and .NET arrays are
converted to JSON arrays. Because JSON arrays only support a range of
values and not properties, any additional properties and fields
declared on .NET collections are not serialized.
So, don't subclass List<T>, just add a second property.
public class MyClass
{
public List<string> Items { get; set; }
public string Name { get; set; }
public MyClass() { Items = new List<string>(); }
}
Here are my thoughts on this. I would think that your expected results would be more consistent with a class like this:
public class MyClass
{
public string Name { get; set; }
public List<string> Items { get; set; }
}
I would not expect to see an Items element in the serialized result for MySubclass : List<string> since there is no Items property on List nor on MySubclass.
Since your class MySubclass is actually a list of strings, I would guess (and I am just guessing here) that the SerializeObject method is merely iterating through your object and serializing a list of strings.

Json.net serialize/deserialize derived types?

json.net (newtonsoft)
I am looking through the documentation but I can't find anything on this or the best way to do it.
public class Base
{
public string Name;
}
public class Derived : Base
{
public string Something;
}
JsonConvert.Deserialize<List<Base>>(text);
Now I have Derived objects in the serialized list. How do I deserialize the list and get back derived types?
You have to enable Type Name Handling and pass that to the (de)serializer as a settings parameter.
Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };
JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);
This will result in correct deserialization of derived classes. A drawback to it is that it will name all the objects you are using, as such it will name the list you are putting the objects in.
If you are storing the type in your text (as you should be in this scenario), you can use the JsonSerializerSettings.
See: how to deserialize JSON into IEnumerable<BaseType> with Newtonsoft JSON.NET
Be careful, though. Using anything other than TypeNameHandling = TypeNameHandling.None could open yourself up to a security vulnerability.
Since the question is so popular, it may be useful to add on what to do if you want to control the type property name and its value.
The long way is to write custom JsonConverters to handle (de)serialization by manually checking and setting the type property.
A simpler way is to use JsonSubTypes, which handles all the boilerplate via attributes:
[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
public virtual string Sound { get; }
public string Color { get; set; }
}
public class Dog : Animal
{
public override string Sound { get; } = "Bark";
public string Breed { get; set; }
}
public class Cat : Animal
{
public override string Sound { get; } = "Meow";
public bool Declawed { get; set; }
}
Use this JsonKnownTypes, it's very similar way to use, it just add discriminator to json:
[JsonConverter(typeof(JsonKnownTypeConverter<BaseClass>))]
[JsonKnownType(typeof(Base), "base")]
[JsonKnownType(typeof(Derived), "derived")]
public class Base
{
public string Name;
}
public class Derived : Base
{
public string Something;
}
Now when you serialize object in json will be add "$type" with "base" and "derived" value and it will be use for deserialize
Serialized list example:
[
{"Name":"some name", "$type":"base"},
{"Name":"some name", "Something":"something", "$type":"derived"}
]
just add object in Serialize method
var jsonMessageBody = JsonSerializer.Serialize<object>(model);

Categories