JSON deserialization after property type changed - c#

I've been working on a C# program that serializes and deserializes some data in JSON format: however, now I have to radically change the type of a pre-existing property, from string to Dictionary(string,string[]).
Is there a way to make it so that, if the program tries to deserialize a document where the field is still a string, it performs some operations to convert it to a Dictionary? Or will the old documents become completely unusable?
For reference, the functions I've been using for reading/writing are the following:
Serialization:
JsonConvert.SerializeObject(this, Formatting.Indented);
Deserialization:
JsonConvert.DeserializeObject<T>(content);
EDIT:
As a simplified version of what I have to do, I have a class with a structure similar to this:
class ExampleClass // V1
{
public string CustomProperty { get; set; }
}
And I need it, after the update, to look like this:
class ExampleClass // V2
{
public Dictionary<string, string[]> CustomProperty { get; set; }
}
What I want is, if I try to deserialize a JSON with the V1 version of the class, instead of raising an exception, that string value will be used to populate the Dictionary (the basic plan is to put a default key and for the array the V1 value split by a default character: I doubt the exact details of the conversion would make much of a difference though). Afterwards, if I try to serialize the resulting object, it will only show the Dictionary, and not the original string.

You could keep two versions of the class you are deserializing into, or you could use a custom JsonConverter and JsonConverterAttribute to deal with the two different ways of deserializing your data.
Either way you choose, you will need some way of identifying what type of document or data type you have, and then you will need an if statement to switch to the appropriate deserialization method. The easiest way of determining this really depends on your situation.

There are some ways on how to handle such a contract change
Keep an old string property, create a new one with another name for the dictionary. Implement the lazy population for the dictionary property
Change the type of the existing property but create a new one and provide [JsonPropertyAttribute] with an old name. Implement the lazy population for the dictionary property
Use JsonConverterAttribute
Have multiple versions of the class (my preferred way)

In the end, I've managed to solve the issue by explicitly writing the deserialization function, like this:
public static ExampleClass DeserializeData(string content)
{
try
{
return JsonConvert.DeserializeObject<ExampleClass>(content);
}
catch
{
// If it's in this section, it's trying to deconvert outdated JSON
Dictionary<string, dynamic> objectMap = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(content);
Dictionary<string, PropertyInfo> propertyMap = new Dictionary<string, PropertyInfo>();
ExampleClass data = new ExampleClass();
foreach (PropertyInfo property in typeof(ExampleClass).GetProperties())
propertyMap.Add(property.Name, property);
foreach (KeyValuePair<string, dynamic> kvp in objectMap)
{
Type propertyType = propertyMap[kvp.Key].PropertyType;
if (kvp.Key != "CustomProperty")
{
if (kvp.Value is Array || kvp.Value is JArray)
{
Type recipientType = propertyType.GetElementType();
Type listType = typeof(List<>).MakeGenericType(recipientType);
System.Collections.IList itemList = (System.Collections.IList)Activator.CreateInstance(listType);
foreach (var item in (kvp.Value as JArray))
itemList.Add(Convert.ChangeType(item, recipientType));
Array itemArray = Array.CreateInstance(recipientType, itemList.Count);
itemList.CopyTo(itemArray, 0);
propertyMap[kvp.Key].SetValue(data, itemArray);
}
else if (Nullable.GetUnderlyingType(propertyType) != null)
{
propertyMap[kvp.Key].SetValue(data, Convert.ChangeType(kvp.Value, Nullable.GetUnderlyingType(propertyType)));
}
else
propertyMap[kvp.Key].SetValue(data, Convert.ChangeType(kvp.Value, propertyType));
}
else
{
if (kvp.Value is string)
{
propertyMap[kvp.Key].SetValue(data, new Dictionary<string, string[]>() { { "DEFAULT", (kvp.Value as string).Split('&') } });
}
else
propertyMap[kvp.Key].SetValue(data, kvp.Value);
}
}
return data;
}
}
With this semi-generic function I can replicate the normal DeserializeObject, and have it elaborate the value depending on its type when it reaches a specific property: not only that, but by having it attempt to use DeserializeObject first, it ensures the fastest alternative is used on properly formatted JSON. It's probably not the most elegant solution, but it works, so I'll take it.
Thank you all for your answers, they were a great help in reaching this solution! Hope this helps others with the same issue as well!

Related

Determining Object Type from JSON

I have an event that gives me a JSON string:
...
public delegate void DataReceivedHandler(string jsonString);
...
public event DataReceivedHandler OnDataReceived = null;
...
if(OnDataReceived != null)
{
OnDataReceived(jsonString);
}
...
That JSON string could be one of 3 different complex objects: LogOnMessage, LogOffMessage, or DataRequest. Each message has a unique set of fields and properties.
How can I determine which object type the JSON string resolves to?
I know I can write a method that iterates through the JProperty.Name of the JObject and find a match by iterating through my collection of objects and their meta-data, but my gut tells me this is a common challenge to be solved so it must be built in to Newtonsoft JSON .NET somewhere that I simply am overlooking or not understanding. It's probably better and faster than my solution would be too...
I was finally able to detect object type by using JObjects and JsonSchemas.
Steps I Took:
Added a Schema property to my message objects that exposed a _schema field. The first time the property is called, it populates _schema with the return value of JsonSchemaGenerator.Generate(object o).
Convert the JSON string into a JObject via the JObject.Parse() static method.
There is an extension method in Newtonsoft.Json.Schema.Extensions that can compare a JObject to a JsonSchema and determine if they match.
Please note: the methods above have been moved to a separate Newtonsoft.Schema library. So my recommendation is to utilize the latest and greatest library.
private Newtonsoft.Json.Schema.JsonSchema _schema;
public static Newtonsoft.Json.Schema.JsonSchema Schema
{
get
{
if (_schema == null)
{
Newtonsoft.Json.Schema.JsonSchemaGenerator generator = new Newtonsoft.Json.Schema.JsonSchemaGenerator();
_schema = generator.Generate(typeof(DataResponse));
}
return _schema;
}
}
...
Newtonsoft.Json.Linq.JObject message = Newtonsoft.Json.Linq.JObject.Parse(json);
if(Newtonsoft.Json.Schema.Extensions.IsValid(message, DataResponse.Schema))
{...}
else if (Newtonsoft.Json.Schema.Extensions.IsValid(message, ServerStatus.Schema))
{...}
...

Any library functions to deserialize JSON automatically and manually?

I'm doing some work for a JIRA REST lib. It uses JSON for communication.
Part of the objects I receive in JSON format are know. The others can be of several different formats. So what I have done is created an object with properties that hide a dictionary
public IssueFields Fields { get; set; }
public IssueType IssueType
{
get { return Fields.IssueType; }
set { Fields.IssueType= value; }
}
IssueFields
{
private Dictionary<string, Field> _fields = new Dictionary<string, Field>();
public string IssueType
{
get { return GetFieldByName<IssueType>(IssueTypeFieldName); }
set { SetFieldByName(IssueTypeFieldName, value); }
}
public T GetFieldByName<T>(string fieldName) where T : class
{
return _fields.ContainsKey(fieldName) ? _fields[fieldName] as T: null;
}
public void SetFieldByName(string fieldName, Field field)
{
if (_fields.ContainsKey(fieldName))
{
_fields[fieldName] = field;
}
else
{
_fields.Add(fieldName, field);
}
}
So I have a bunch of classes like that. I can deserialize into them no problem since JavaScriptSerializer (or any other JSON deserializer) just takes the values and puts them into properties of the objects objects automatically. However there are a bunch of unknown fields all starting with "customField_XXXXX".
What I am currently doing is overriding the JavaScriptSerializer and manually putting EVERYTHING into place. Another idea I got from someone else's code was to re-serialize the dictionary inside the JavaScriptConverter override, then deserialize it into the issue, then put everything else in manually from the dictionary, but that adds a lot of overhead and certainly will raise more than a few eyebrows.
public class MyConverter : JavaScriptConverter
{
private static JavaScriptSerializer _javaScriptSerializer;
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
string json = _javaScriptSerializer.Serialize(dictionary);
Issue issue = _javaScriptSerializer.Deserialize<Issue>(json);
// Then add the rest of my objects manually
Is there any way to get the object back with whatever it could serialize AND the dictionary so I can fill in anything it couldn't on my own? I just haven't been able to find anything other than this method....
Thanks!
Json.Net has a built-in "Extension Data" feature that might solve your problem. After deserializing known properties into your class, it automatically handles adding any "extra" object properties to a dictionary. You can read about it on the author's blog. Also check out this answer for some example code.

Losing collection objects that are passed to a web service as properties of another object

I have a very complex ASP.Net web service that I am having to develop. Below is sample code that I've written.
First, I have a web service method that takes a List as a parameter.
[WebMethod] public AppResults SyncObjectToServer(string appID, List updates)
WSExecCommand is a class that I've defined that contains a multiple List objects. Here is a simplistic version of WSExecCommand.
[Serializable] public class WSExecCommand
{
public string Command;
[XmlIgnore] public Dictionary<string, MyKeyValuePair> __ParentKey;
[XmlArray] public List<MyKeyValuePair> ParentKey
{
get
{
if (__ParentKey == null)
__ParentKey = new Dictionary<string, MyKeyValuePair>;
return new List<KeyValuePair>(__ParentKey.Values);
}
set
{
__ParentKey.Clear();
foreach (MyKeyValuePair kvp in value)
__ParentKey.Add(kvp.Key, kvp);
}
}
}
If you are wondering why I've setup my class this way, the problem is that ASP.Net doesn't allow for you to return or receive Dictionary objects. I really need for my ParentKey object to be a dictionary. However, to get around the limitations of ASP.Net, I created the above wrapper property to get and set the values of the underlying Dictionary, which I reference in my code.
My problem is that when I call my web service method from my consuming application, __ParentKey is never populated with the data that is being passed to the server.
I've stepped into the Web Service code, and I am getting a populated List<WSExecCommand> collection. However, for each WSExecCommand in the list, all List<T> type objects have 0 items.
How can I resolve this issue?
get
{
if (__ParentKey == null)
__ParentKey = new Dictionary<string, MyKeyValuePair>;
return new List<KeyValuePair>(__ParentKey.Values);
}
Looks like here you're setting __ParentKey to a new Dictionary, and then returning a list of the KeyValuePairs in that dictionary, which of course would be empty the first time you call it. Since you've chosen to not serialize __ParentKey (which you can't anyways, because Dictionaries are not serializable), when the instance of WSExecCommand is deserialized on the other end of the wire, then __ParentKey is null (which then results in an empty list the first time you try to access the property).
EDIT
It is possible that the setter is being called on deserialization like you'd expect. However, if that is the case then your problem is likely the fact that __ParentKey is null when you construct the object, and does not get instantiated until you call the getter the first time. I do not believe that your getter would be called during deserialization, and as a result the instance of your dictionary is never created. Try constructing your dictionary up front, and that way you could remove the null check from your getter as well.
[Serializable] public class WSExecCommand
{
public string Command;
[XmlIgnore] public Dictionary<string, MyKeyValuePair> __ParentKey =
new Dictionary<string, MyKeyValuePair>();
[XmlArray] public List<MyKeyValuePair> ParentKey
{
get
{
return new List<KeyValuePair>(__ParentKey.Values);
}
set
{
__ParentKey.Clear();
foreach (MyKeyValuePair kvp in value)
__ParentKey.Add(kvp.Key, kvp);
}
}
}

How to write a getter and setter for a Dictionary?

How do you define a getter and setter for complex data types such as a dictionary?
public Dictionary<string, string> Users
{
get
{
return m_Users;
}
set
{
m_Users = value;
}
}
This returns the entire dictionary? Can you write the setter to look and see if a specific key-value pair exists and then if it doesn't, add it. Else update the current key value pair? For the get, can you return a specific key-value pair instead of the whole dictionary?
Use an indexer property (MSDN):
public class YourClass
{
private readonly IDictionary<string, string> _yourDictionary = new Dictionary<string, string>();
public string this[string key]
{
// returns value if exists
get { return _yourDictionary[key]; }
// updates if exists, adds if doesn't exist
set { _yourDictionary[key] = value; }
}
}
Then use like:
var test = new YourClass();
test["Item1"] = "Value1";
It is not possible to do it in a way that would involve only properties. You theoretically could write a setter, but for a getter, you would need to specify a key that you want to retrieve. That is impossible since properties do not accept parameters. Natural way to accomplish what you want would be to use methods:
private Dictionary<string, string> users = new Dictionary<string, string>();
public void Set(string key, string value)
{
if (users.ContainsKey(key))
{
users[key] = value;
}
else
{
users.Add(key, value);
}
}
public string Get(string key)
{
string result = null;
if (users.ContainsKey(key))
{
result = users[key];
}
return result;
}
Alternatively, as others have already said, you could use indexers, but I've always found them a little cumbersome. But I guess it's just a matter of personal preference.
And just for the sake of completeness, this is how a setter could look like, although it's highly unusual and counter-intuitive to have such a property:
public KeyValuePair<string, string> Users
{
set
{
Set(value.Key, value.Value);
}
}
Internally, it uses the Set method from my previous snippet.
It looks like you want an "named indexer". Here's (my) one way to accomplish that using C#.
My approach exposes a property that returns an object (with a default indexer) which will perform the indexing into the appropriate field given the lambdas to do it.
There are reasons you may or not want to use this method, but I'll leave that to you. :)
You won't be able to do that with a property. You'll need to use methods for that, or add an indexer to your class. The get method can't accept a parameter (the key).
Another option, if you want someone to be able to easily add/remove keys to the dictionary but prevent them from setting an entirely new one would be to make the property a read-only property that returns a dictionary created in the constructor. It would be less flexible then adding get/set methods, but in common, simple cases it can do just fine.
It is possible to do so with the setter but highly unrecommended, and is completely impossible with the getter as it takes no parameter to determine what to get.
For the setter you would have to pass a Dictionary<string, string> with a single pair but it goes against what you would expect the getter/setter to usually do and completely stops you setting the entire Dictionary.
A much better way is to use a pair of methods which you can name Get and Set if you so desire.
Dictionary<string, string> param = new Dictionary<string, string>();
public void SetYourParameter(string parametrName, string paramValue)
{
param[parametrName] = paramValue;
}
public string GetYourParameter(string parametrName)
{
// ContainKey ---> It returns value if the key was found
if( param.ContainsKey(parametrName))
return param[parametrName];
else
return null;
}

How to write a class that (like array) can be indexed with `arr[key]`?

Like we do Session.Add("LoginUserId", 123);
and then we can access Session["LoginUserId"], like an Array, how do we implement it?
You need an indexer:
public Thing this[string index]
{
get
{
// get the item for that index.
return YourGetItemMethod(index)
}
set
{
// set the item for this index. value will be of type Thing.
YourAddItemMethod(index, value)
}
}
This will let you use your class objects like an array:
MyClass cl = new MyClass();
cl["hello"] = anotherObject;
// etc.
There's also a tutorial available if you need more help.
Addendum:
You mention that you wanted this to be available on a static class. That get's a little more complicated, because you can't use a static indexer. If you want to use an indexer, you'd need to access it off of a static Field or some such sorcery as in this answer.
You should use indexers
See the link:
http://msdn.microsoft.com/en-us/library/2549tw02.aspx
Sounds like all you need is a generic dictionary.
var session = new Dictionary<string, object>();
//set value
session.Add("key", value);
//get value
var value = session["key"] as string;
If you want to make this static, just make it a static member in another class.
public static class SharedStorage
{
private static Dictionary<string, object> _data = new Dictionary<string,object>();
public static Dictionary<string, object> Data { get { return _data; } }
}
Then you can access it as such, without having to initialize it:
SharedStorage.Data.Add("someKey", "someValue");
string someValue = (string) SharedStorage.Data["someKey"];
If you want to be more adventurous and are using .NET 4 you can also use an Expando Object, like the ViewBag member available to controllers in ASP.NET MVC 3:
dynamic expando = new ExpandoObject();
expando.UserId = 5;
var userId = (int) expando.UserId;
With the way you usually use the Session variable all you really need is a generic Dictionary collection like this one. You don't really need to write a class. But if you need to add extra functionality and/or semantics you could certainly wrap the collection with a class and just include and indexer.
For other collections check out the Collections.Generic namespace.

Categories