Custom JSON serialization/deserialization to read property in multiple possible formats - c#

My application reads JSON files created using a format like this:
{
"myProperty": {complex JSON here}
}
class MyClass
{
public MyChildClass MyProperty {get; set;}
}
I need to change the way the class works, so that it looks like this instead:
class MyClass
{
public MyNewChildClass MyNewProperty {get; set;}
}
and I need to be able to support files created in the older format, but also support a file if it was created using the new format.
I have the code to convert a MyChildClass object into a MyNewChildClass object, but how can I set up the serialization so that the object can deserialize the old format, changing the property name and type from MyChildClass MyProperty to MyNewChildClass MyNewProperty AND just use the new format when serializing AND deserialize using the new format if that is what the JSON file contains?

If you use both properties, the json will work for both, new and old.
class MyClass
{
[JsonPropert("myProperty")]
public MyChildClass MyProperty {get; set;}
[JsonProperty("myNewProperty")] // -> Remember, case matters.
public MyNewChildClass MyNewProperty {get; set;}
}
When you deserialize the class, add a check to see which is not null and work with that (different methods for each i guess). This should help you keep the breaking change to a minimum.
BTW>. if you have the code that converts the new to the old or vice versa, you can check if the value of old is null, then run that process/method you have to convert new to old and continue on with the object. Remember, it would have to be After the deserialization,
var properties = JsonConvert.DeserializeObject<MyClass>("data");
if (properties.MyNewProperty == null)
{
properties = myMethodToConvertOldToNew(properties);
}
public MyClass myMethodToConvertOldToNew(MyClass)
{
if (properties.New == null)
{
properties.New = ConversionMethod(properties.Old, properties.New);
// dont have to, but,
properties.Old = null;
}
return properties.
}

Why not add a private property to your class that will set your new property:
class MyClass
{
[JsonProperty]
private MyChildClass MyProperty { set => MyNewProperty = YourConversionMethod(value); }
public MyNewChildClass MyNewProperty { get; set; }
}
The JsonProperty attribute will ensure that the private setter is used.

Related

Serialize and deserialize Json property to two different values

I have a source JSON object that I get from a server that looks like this:
{
"Property A": .098
}
I have a C# class that I deserialize the JSON into using the following:
public class foo {
[JsonProperty("Property A")]
public decimal PropertyA{ get; set;}
}
var ret = JsonConvert.DeserializeObject<foo>(File.ReadAllText(someJsonString);
This allows me to read in a JSON file and convert it to a C# object. But now I would like to write this object out to JSON but change the format to be this:
{
"property_a":.098
}
I currently have another class that is identical and the only difference is that I change the JsonProperty field to the new desired name:
public class foo {
[JsonProperty("property_a")]
public decimal PropertyA{ get; set; }
}
Is there a way using Newtonsoft to have a JsonProperty read as a particular value but write as a different?

Set Default value for missing Complex properties with JSON.net (JsonConvert.SerializeObject or JsonConvert.DeSerializeObject)

I have a requirement where I need to set default value to the below complex property Instances using JsonProperty and DefaultValue.
I know we can achieve this for primitive properties as mentioned in the below link, but need to know how we can do it for complex properties.
Default value for missing properties with JSON.net
Below is the default Instances value I need to set using DefaultValue(). Please let me know how to achieve this.
Default value to be set to Instances property:
Instance instance = new Instance();
instance.Name = "XYZ";
instance.MyProperty = 11;
List<Instance> Instances = new List<Instance>();
Instances.Add(instance);
Code snippet:
public class DataSettings
{
public DataSettings()
{
Instances = new List<Instance>();
}
[DefaultValue()] //How can I mention the above default value here ?
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public List<Instance> Instances { get; set; }
}
public class Instance
{
public string Name { get; set; }
public int MyProperty { get; set; }
}
As you've seen, attributes only support constant values, so you cannot set a complex value in an attribute. If you want to set a default value for a complex property during deserialization, a good approach is to use a serialization callback method, as shown below.
The idea is to add a method to your class which the serializer will call after deserialization is complete for the object. The callback must be a void method that accepts a StreamingContext as its only parameter, and it must be marked with an [OnDeserialized] attribute. The name of the method does not matter.
Inside the callback method you can check whether the Instances list was populated, and if not, you can set the default value as you require.
public class DataSettings
{
public DataSettings()
{
Instances = new List<Instance>();
}
public List<Instance> Instances { get; set; }
[OnDeserialized]
internal void SetDefaultValuesAfterDeserialization(StreamingContext context)
{
if (Instances == null || !Instances.Any())
{
Instances = new List<Instance>
{
new Instance { Name = "XYZ", MyProperty = 11 }
};
}
}
}
Here is a working fiddle to demonstrate the concept: https://dotnetfiddle.net/uCGP5X

Json.NET : Detect an absence of a property on Json which appears to be a member of my object

I'm trying to deserialize some Json objects using Json.NET. I'd like to be able to detect if I have a member in my class missing from the Json properties for that object. For instance, I have a class which looks like this :
public class MyClass
{
public int n;
public bool b;
public string s;
}
And a Json which looks like this
{"n":1,"b":true}
so it's missing the "s" property. When I try to deserialize that, members which are not in the Json will have default value. So "s" will be equal to null. Fair enough, but is it possible to detect that when I'm deserializing ?
In substance, I want to do pretty much the opposite of this Stackoverflow post
But in my case, the MissingMemberHandling setting seems to do nothing, sadly.
Json.Net provides a way to achieve that.
You can set an attribute on the property in your Model class. and if
that property is not available in JSON it'll throw an exception.
Here is the Example
Model
public class Videogame
{
[JsonProperty(Required = Required.Always)]
public string Name { get; set; }
[JsonProperty(Required = Required.AllowNull)]
public DateTime? ReleaseDate { get; set; }
}
Test Code
string json = #"{
'Name': 'Starcraft III'
}";
Videogame starcraft = JsonConvert.DeserializeObject<Videogame>(json);
You can read more about this here
This is what i can think of at the moment.
public void CheckData()
{
if(n == null)
{
Console.Write("n is null");
}
if (b == null)
{
Console.Write("b is null");
}
if (s == null)
{
Console.Write("s is null");
}
}
Run a method like that after json maps to the object to see if there were any still null.
Or you can try something like this
var fields = typeof(MyClass).GetFields();
foreach (var field in fields)
{
if(!json.ContainsKey(field.Name))
{
Console.Write($"{field.Name} is missing");
}
}

Deserialize string by class name

Let's say I have a Value that is deserialized from a class.
public class MyValue
{
public string MyPropertyA { get; set; }
public string MyPropertyB { get; set; }
public string DeserializationClass { get; } = typeof(MyValue).Name;
}
I serialize this using JsonConvert class. MyValue class has a property DeserializationClass that should be used as info from which class the string was serialized from. In other words, when I deserialize the string into an object, this property serves as info which class should be used to deserialize the string. However, I am kinda stuck here as I am not sure how to get back the class from the string. Can anybody help me here?
public class Program
{
void Main()
{
var serialized = Serialize();
var obj = Deserialize(serialized);
}
string Serialize()
{
var objValue = new MyValue { MyPropertyA="Something", MyPropertyB="SomethingElse" };
return JsonConvert.SerializeObject<MyClass>(value);
}
object Deserialize(string serialized)
{
//How to deserialize based on 'DeserializationClass' property in serialized string?
return = JsonConvert.Deserialize<???>(serialized);
}
}
EDIT: Modified example to make it more clear what I need as I don't have access to objValue when I need to deserialize the string.
probably you might need to use JsonSerializerSettings.
What you might need to do is
JsonSerializerSettings setting = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
};
and then while serializing use this setting.
var serialized = JsonConvert.SerializeObject(objValue,setting);
this will give you Json like this
{"$type":"WPFDatagrid.MyValue, WPFDatagrid","MyPropertyA":"Something","MyPropertyB":"SomethingElse","DeserializationClass":"MyValue"}
from this you can find the name of the class used it to actually get your type.
Hope this helps !!
There is an overload
If your Type is in form of a Namespace, you can obtain the type from a string representation:
Type objValueType = Type.GetType("Namespace.MyValue, MyAssembly");
object deserialized = JsonConvert.Deserialize(objValueType, serialized);

How to serialize/deserialize a DateTime stored inside an object field using DataContractJsonSerializer?

I use the following class to exchange JSON data over two ASP.NET services :
[DataContract]
public class Filter
{
[DataMember]
public string Name {get; set;}
[DataMember]
public FilterOperator Operator {get; set;}
[DataMember]
public object Value {get; set;}
}
Here is the problem : if I set a DateTime inside Value, it will be deserialized as string :
Value = "/Date(1476174483233+0200)/"
This is probably because deserializer has no clue to know what was the type of the value when serialized initially :
JSON = {"Value":"\/Date(1476174483233+0200)\/"}
As explained here, DataContractJsonSerializer supports polymorphism, with the help of the __type property.
I have tried to add [KnownType(typeof(DateTime))] attribute on the top of the class but it does not help.
However if I set a Tuple<DateTime> inside Value property (and the appropriate KnownType attribute on the class), it works (the value it deserialized properly) :
Value = {(10/11/2016 10:49:30 AM)}
Inside JSON, __type is emited
JSON = {
"Value": {
"__type" : "TupleOfdateTime:#System",
"m_Item1" : "\/Date(1476175770028+0200)\/"
}
}
Is there a way to force DataContractJsonSerializer to emit proper information to serialize/deserialize DateTime properly (which mean I got a DateTime after serialization instead of a string) ?
I have try to set EmitTypeInformation = EmitTypeInformation.Always in DataContractJsonSerializerSettings but it does not help.
The problem is that DataContractJsonSerializer only inserts a polymorphic type hint property "__type" for types that correspond to a JSON object - an unordered set of name/value pairs surrounded by { and }. If the type maps to anything else (i.e. a JSON array or primitive) then there is no place for a type hint to be inserted. This restriction is documented in Stand-Alone JSON Serialization:
Type Hints Apply Only to Complex Types
There is no way to emit a type hint for non-complex types. For example, if an operation has an Object return type but returns a Circle, the JSON representation can be as shown earlier and the type information is preserved. However, if Uri is returned, the JSON representation is a string and the fact that the string used to represent a Uri is lost. This applies not only to primitive types but also to collections and arrays.
Thus what you will need to do is to modify your Filter class to serialized and deserialized a generic surrogate object for its value that encapsulates the value's type information, along the lines the one in this question for Json.Net:
[DataContract]
public class Filter
{
[DataMember]
public string Name { get; set; }
[DataMember]
public FilterOperator Operator { get; set; }
[IgnoreDataMember]
public object Value { get; set; }
[DataMember]
TypedSurrogate TypedValue
{
get
{
return TypedSurrogate.CreateSurrogate(Value);
}
set
{
if (value is TypedSurrogate)
Value = ((TypedSurrogate)value).ObjectValue;
else
Value = value;
}
}
}
[DataContract]
// Include some well-known primitive types. Other types can be included at higher levels
[KnownType(typeof(TypedSurrogate<string>))]
[KnownType(typeof(TypedSurrogate<bool>))]
[KnownType(typeof(TypedSurrogate<byte>))]
[KnownType(typeof(TypedSurrogate<sbyte>))]
[KnownType(typeof(TypedSurrogate<char>))]
[KnownType(typeof(TypedSurrogate<short>))]
[KnownType(typeof(TypedSurrogate<ushort>))]
[KnownType(typeof(TypedSurrogate<int>))]
[KnownType(typeof(TypedSurrogate<long>))]
[KnownType(typeof(TypedSurrogate<uint>))]
[KnownType(typeof(TypedSurrogate<ulong>))]
[KnownType(typeof(TypedSurrogate<float>))]
[KnownType(typeof(TypedSurrogate<double>))]
[KnownType(typeof(TypedSurrogate<decimal>))]
[KnownType(typeof(TypedSurrogate<DateTime>))]
[KnownType(typeof(TypedSurrogate<Uri>))]
[KnownType(typeof(TypedSurrogate<Guid>))]
[KnownType(typeof(TypedSurrogate<string[]>))]
public abstract class TypedSurrogate
{
protected TypedSurrogate() { }
[IgnoreDataMember]
public abstract object ObjectValue { get; }
public static TypedSurrogate CreateSurrogate<T>(T value)
{
if (value == null)
return null;
var type = value.GetType();
if (type == typeof(T))
return new TypedSurrogate<T>(value);
// Return actual type of subclass
return (TypedSurrogate)Activator.CreateInstance(typeof(TypedSurrogate<>).MakeGenericType(type), value);
}
}
[DataContract]
public class TypedSurrogate<T> : TypedSurrogate
{
public TypedSurrogate() : base() { }
public TypedSurrogate(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
[DataMember]
public T Value { get; set; }
}
Now your JSON will look something like:
{
"TypedValue": {
"__type": "TypedSurrogateOfdateTime:#Question39973917",
"Value": "/Date(1476244800000)/"
}
}

Categories

Resources