JSON need nested array for JavaScriptSerializer without naming the element - c#

I would like to use JavaScriptSerializer for this because I'm afraid of how many things might break and I can't change anything on the client-side.
If Json.Net is the best way to go then I will try it but I need an example.
I have this class
Class Definition
[DataContract]
[Serializable]
public class Family
{
[DataMember(Order = 0)]
public List<Member> members { get; set; }
}
[DataContract]
[Serializable]
public class Member
{
[DataMember(Order = 0)]
public string FName { get; set; }
[DataMember(Order = 1)]
public string LName { get; set; }
[DataMember(Order = 2)]
public string DOB { get; set; }
[DataMember(Order = 3)]
public string Gender { get; set; }
[DataMember(Order = 4)]
public string Type { get; set; }
}
The JSON I am Deserializing looks like this
JSON Example
[
{
"Family": [
{
"FName": "Jane",
"LName": "Prospect",
"DOB": "04/01/1980",
"Gender": "Female",
"Type": "Adult"
},
{...}
]
},
{
"OptionChoice": 34,
"OptionText": "Aquatics"
},
{...},
{...}
]
I can deserialize the Answer objects fine (OptionChoice,OptionText).
Hower the answer object has an additional item full of nulls where it is parsing the Family section of the JSON. I don't really want that.
When I try to deserialize the Family part I get an error
Type 'Family' is not supported for deserialization of an array.
It says it has a null for the Family.members. Is it looking for "Family": [ "members": {...},{...}] ?
How can I get this working without changing the JSON example?
Update After dbc Answer:
Here is my model
[DataContract]
[Serializable]
public class Answer
{
[DataMember(Order = 2, EmitDefaultValue = false)]
public int FormID { get; set; }
[DataMember(Order = 3,EmitDefaultValue = false)]
public int Question { get; set; }
[DataMember(Order = 5)]
public int OptionChoice { get; set; }
[DataMember(Order = 6,IsRequired = false)]
public string OptionText { get; set; }
[DataMember(Order = 5, EmitDefaultValue = false)]
public bool lockAnswer { get; set; }
[DataMember(Order = 1,EmitDefaultValue= false)]
public List<FamilyMember> Family { get; set; }
}
[DataContract]
[Serializable]
public class FamilyMember
{
[DataMember(Order = 0)]
public string FName { get; set; }
[DataMember(Order = 1)]
public string LName { get; set; }
[DataMember(Order = 2)]
public string DOB { get; set; }
[DataMember(Order = 3)]
public string Gender { get; set; }
[DataMember(Order = 4)]
public string Type { get; set; }
}
At this point in my creation of my SO update I solved the problem with Rubber Duck Debugging
Turns out I had some regex that was stripping all "[" & "]" then manually adding them back but only on the ends.
This cool comparison tool at http://pro.jsonlint.com/ helped btw.
Thanks! If anybody in the future reads this and wants to show a testable way to do this in JSON.net (I tried but got stuck) please do.

Your question includes two related questions:
Exception loading the Family list.
The problem here is that, as you suspect, there is no property corresponding to members. What your JSON has is an array of objects, each of which might have an array-valued property Family. Thus your data model should look like:
public class ResponseItem
{
public int? OptionChoice { get; set; }
public string OptionText { get; set; }
public List<FamilyMember> Family { get; set; }
// Other fields not shown from {...}
}
public class FamilyMember
{
public string FName { get; set; }
public string LName { get; set; }
public string DOB { get; set; }
public string Gender { get; set; }
public string Type { get; set; }
}
You state, "Hower the answer object has an additional item full of nulls where it is parsing the Family section of the JSON. I don't really want that." This could be done by deserializing the JSON into a polymorphic array where each possible derived type has just the minimal number of fields. However, since there is no __type information in your JSON, you will need to add some slightly fussy logic to chose the correct concrete type for each array element. Your data model would look like:
public interface IResponseItem // base interface for all possible responses
{
}
public class FamilyResponse : IResponseItem
{
public List<FamilyMember> Family { get; set; }
}
public class OptionsResponse : IResponseItem
{
public int OptionChoice { get; set; }
public string OptionText { get; set; }
}
In complex serialization situations like this people seem to prefer Json.NET, nevertheless it's still possible with JavaScriptSerializer as per your question. You must code up a JavaScriptConverter to select the appropriate derived type from the base IResponseItem type by matching the property names, for instance:
public class PolymorphicTypeConverter : JavaScriptConverter
{
public Type BaseType { get; private set; }
public Type[] DerivedTypes { get; private set; }
public PolymorphicTypeConverter(Type baseType, IEnumerable<Type> derivedTypes)
{
this.BaseType = baseType;
this.DerivedTypes = derivedTypes.ToArray();
}
static MemberInfo FindMember(Type type, string name)
{
try
{
var propInfo = type.GetProperty(name,
BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
if (propInfo != null
&& propInfo.GetSetMethod() != null
&& propInfo.GetIndexParameters().Length == 0)
return propInfo;
var fieldInfo = type.GetField(name,
BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
if (fieldInfo != null)
return fieldInfo;
}
catch (AmbiguousMatchException)
{
return null;
}
return null;
}
IEnumerable<Type> AncestorsAndSelf(Type type)
{
for (; type != null; type = type.BaseType)
if (DerivedTypes.Contains(type))
yield return type;
}
Type FindUniqueTypeMatch(IDictionary<string, object> jsonProperties)
{
List<Type> matches = new List<Type>();
foreach (var type in DerivedTypes)
{
if (type.IsInterface)
continue; // Bug?
bool isMatch = true;
foreach (var name in jsonProperties.Keys)
{
if (FindMember(type, name) == null)
{
isMatch = false;
break;
}
}
if (isMatch)
{
matches.Add(type);
}
}
if (matches.Count == 0)
return null;
else if (matches.Count == 1)
return matches[0];
else
{
// Multiple matches.
// If there is a common base type to all matches, return it. Otherwise, give up.
var candidates = AncestorsAndSelf(matches[0]).Reverse();
foreach (var match in matches.Skip(1))
{
candidates = candidates.Zip(AncestorsAndSelf(match).Reverse(), (t1, t2) => (t1 == t2 ? t1 : null)).Where(t => t != null);
}
return candidates.LastOrDefault();
}
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var subtype = FindUniqueTypeMatch(dictionary);
if (subtype == null)
throw new JsonSerializationException();
var method = serializer.GetType().GetMethod("ConvertToType");
var generic = method.MakeGenericMethod(subtype);
return generic.Invoke(serializer, new object [] { dictionary } );
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
// Should never be called.
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get
{
return new Type[] { BaseType };
}
}
}
This only works when each object in your JSON array has properties that match one and only one type in the derived type array. If this cannot be guaranteed, because, for instance, null fields were not serialized leading to multiple matches, you will need to enhance the converter to make a best guess match.
Then call it like:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new PolymorphicTypeConverter(typeof(IResponseItem), new Type[] { typeof(FamilyResponse), typeof(OptionsResponse) }) });
var responseArray = serializer.Deserialize<IResponseItem[]>(json);

Related

Json conversion conundrum due to mixed type in List

All - I've stumbled into a scenario that's causing me quite a bit of grief. I have a json structure (produced by gateio api) which on the face of it, looks super simple to deserialize into an object. the jsonTickerString looks like the below:
{
"method":"ticker.update",
"params":[
"BTC_USDT",
{
"period":86400,
"open":"46721.06",
"close":"48130.43",
"high":"48758.59",
"low":"46330.3",
"last":"48130.43",
"change":"2.95",
"quoteVolume":"2246.8399550054",
"baseVolume":"106183751.468785134437"
}
],
"id":null
}
However, this is proving to be deceptively funky when trying to push it into an object model. I derived the object model below and thought we were all set to go:
public partial class GateIoTicker
{
[JsonProperty("method")]
public string Method { get; set; }
[JsonProperty("params")]
public List<ParamElement> Params { get; set; }
[JsonProperty("id")]
public object Id { get; set; }
}
public class ParamClass
{
[JsonProperty("period")]
public long Period { get; set; }
[JsonProperty("open")]
public string Open { get; set; }
[JsonProperty("close")]
public string Close { get; set; }
[JsonProperty("high")]
public string High { get; set; }
[JsonProperty("low")]
public string Low { get; set; }
[JsonProperty("last")]
public string Last { get; set; }
[JsonProperty("change")]
public string Change { get; set; }
[JsonProperty("quoteVolume")]
public string QuoteVolume { get; set; }
[JsonProperty("baseVolume")]
public string BaseVolume { get; set; }
}
public partial struct ParamElement
{
public string coinName;
public ParamClass quoteParams;
public static implicit operator ParamElement(ParamClass quoteparams)
{
return new ParamElement { quoteParams = quoteparams };
}
public static implicit operator ParamElement(string coinname)
{
return new ParamElement { coinName = coinname };
}
}
I then set about populating the object using the standard Json.Net approach:
var gateIoTicker = JsonConvert.DeserializeObject<GateIoTicker>(jsonTickerString);
However, although this correctly deserializes the string element in the "params" object, no amount of coersion will bring about a deserialization of the ParamClass object.
Am I missing something very obvious here?? I've spent an inordinate amount of time trying to figure this out and think it's now time to solicit some superior brain power.
Hope this scans as expected...
[Edit] - further to Serge's suggestion, i took his code and added it as a method on my GatIoTicker object. Would have preferred an option that desrializes using attributes, but this works perfectly. Refactored code looks like:
public partial class GateIoTicker
{
[JsonProperty("method")]
public string Method { get; set; }
[JsonProperty("params")]
public List<ParamElement> Params { get; set; }
[JsonProperty("id")]
public object Id { get; set; }
public GateIoTicker FromJson(string json)
{
var jsonObject = JObject.Parse(json);
var pars = jsonObject["params"] as JArray;
var paramElement = new ParamElement();
foreach (var jObject in pars)
{
if (jObject.GetType().Name.ToString() == "JValue") paramElement.ParamName = ((JValue)jObject).ToString();
else
{
paramElement.ParamBody = jObject.ToObject<ParamClass>();
}
}
GateIoTicker gateIoTicker = new GateIoTicker { Params = new List<ParamElement>() };
gateIoTicker.Id = (string)jsonObject["Id"];
gateIoTicker.Method = (string)jsonObject["method"];
gateIoTicker.Params.Add(paramElement);
return gateIoTicker;
}
}
public partial class ParamElement
{
public string ParamName { get; set; }
public ParamClass ParamBody {get; set;}
}
thanks again for the suggestions and nudges. seasons greetings
Try this, it was tested in Visual studio
var jsonObject = JObject.Parse(json);
var pars = jsonObject["params"] as JArray;
var paramElement = new ParamElement();
foreach (var jObject in pars)
{
if (jObject.GetType().Name.ToString() == "JValue") paramElement.ParamName = ((JValue)jObject).ToString();
else
{
paramElement.ParamBody=jObject.ToObject<ParamClass>();
}
}
GateIoTicker gateIoTicker = new GateIoTicker {Params= new List<ParamElement>()};
gateIoTicker.Id= (string) jsonObject["Id"];
gateIoTicker.Method= (string) jsonObject["method"];
gateIoTicker.Params.Add(paramElement);
ParamElement class
public partial class ParamElement
{
public string ParamName { get; set; }
public ParamClass ParamBody {get; set;}
}

How to compare object and List<> and object.value null when the value doesn't exist in list

I need to compare between my object and a list.
List contains attribute "nom_colonne", it fill by a query and the result is a list of attributes (same like my object AppareilsReparations)
For example :
if droit_utilisateur.nom_colonne = "Num_dossier"
so i keep the value in arp.num_dossier
But if i don't have this value of my list droit_utilisateur :
arp.num_dossier will be null.
I wanted to cast my object with System.Collections.IList but impossible to cast. I have an error.
public class AppareilsReparations
{
public string Num_dossier { get; set; }
//public string reference_aff { get; set; }
public int Id { get; set; }
public Guid Uid { get; set; }
public string ref_sav { get; set; }
public CodeDefaut codedefaut { get; set; }
public CodeSymptome codesymptome { get; set; }
}
public class Droits
{
public int id { get; set; }
public int utilisateur_id { get; set; }
public string nom_table { get; set; }
public string nom_colonne { get; set; }
}
AppareilsReparations arp = db.Query<AppareilsReparations>
("select * from v_appareils_reparations where ref_sav_client =#ref_sav", new { ref_sav }).SingleOrDefault();
List<Droits> droit_utilisateur = GetDroits("admin");
//var appareil = new List<AppareilsReparations>();
IList appareil = (IList)arp;
var result = droit_utilisateur.Where(x => !appareil.Contains(x.nom_colonne)).ToList();
That's cause AppareilsReparations is not a type of IList and thus your cast would always fail IList appareil = (IList)arp;. Probably you wanted to do like below; just comparing with Num_dossier field of AppareilsReparations type
var result = droit_utilisateur.Where(x => x.nom_colonne == arp.Num_dossier).ToList();
i have found my problem, i post my code for everybody :
List<Droits> droit_utilisateur = GetDroits(username);
Type myType = e.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in e.GetType().GetProperties())
{
object propValue = prop.Name;
if (droit_utilisateur.Any(s => s.nom_colonne.Contains(prop.Name/*, StringComparison.OrdinalIgnoreCase)*/) == false))
{
prop.SetValue(e, Convert.ChangeType(null, prop.PropertyType), null);
}
}
return e;

Json.net deserialization is returning an empty object

I'm using the code below for serialization.
var json = JsonConvert.SerializeObject(new { summary = summary });
summary is a custom object of type SplunkDataModel:
public class SplunkDataModel
{
public SplunkDataModel() {}
public string Category { get; set; }
public int FailureCount { get; set; }
public Dictionary<string, SplunkError> FailureEntity { get; set; }
public Dictionary<string, string> JobInfo { get; set; }
public string JobStatus { get; set; }
public int SuccessCount { get; set; }
public List<string> SuccessEntity { get; set; }
public int TotalCount { get; set; }
}
Serialization results in the JSON below:
{
"summary": {
"Category": "category",
"JobStatus": "Failure",
"JobInfo": {
"Course processing failed": ""
},
"TotalCount": 0,
"SuccessCount": 0,
"FailureCount": 0,
"FailureEntity": {},
"SuccessEntity": []
}
}
Now, for unit testing purposes, I need to deserialize it, but the code below is returning an object with empty values. Where am I going wrong?
var deserialized = JsonConvert.DeserializeObject<SplunkDataModel>(contents);
On my side, it was because I had no public setter for my properties.
Instead of having
public class MyClass
{
public int FileId { get; }
}
I should have
public class MyClass
{
public int FileId { get; set; }
}
silly mistake that cost me hours....
When you serialized your SplunkDataModel to JSON, you wrapped it in an object with a summary property. Hence, when you deserialize the JSON back to objects, you need to use the same structure. There are several ways to go about it; they all achieve the same result.
Declare a class to represent the root level of the JSON and deserialize into that:
public class RootObject
{
public SplunkDataModel Summary { get; set; }
}
Then:
var deserialized = JsonConvert.DeserializeObject<RootObject>(contents).Summary;
Or, deserialize by example to an instance of an anonymous type, then retrieve your object from the result:
var anonExample = new { summary = new SplunkDataModel() };
var deserialized = JsonConvert.DeserializeAnonymousType(contents, anonExample).summary;
Or, deserialize to a JObject, then materialize your object from that:
JObject obj = JObject.Parse(contents);
var deserialized = obj["summary"].ToObject<SplunkDataModel>();

What am I doing wrong with JSON.NET's JsonConvert

json string
{
"success": true,
"challenge_ts": "2016-11-03T17:30:00Z",
"hostname": "mydomain.com"
}
class
internal class reCaptchaResponse
{
internal bool success { get; set; }
internal DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
internal string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
internal string[] error_codes { get; set; } // optional
}
attempt to serialize
reCaptchaResponse responseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<reCaptchaResponse>(jsonResult);
attempt fails like...
Newtonsoft.Json.JsonConvert.SerializeObject(responseObject) returns
{}
Json.Net, by default, only serializes/deserialzes public fileds and properties, but you can also do it without changing access modifiers from internal to public.
Just use JsonProperty attribute
internal class reCaptchaResponse
{
[JsonProperty]
internal bool success { get; set; }
[JsonProperty]
internal DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
[JsonProperty]
internal string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
[JsonProperty]
internal string[] error_codes { get; set; } // optional
}
(Without modifing the original class) You can even use ContractResolver to select which properties/fields should be used in serialization process
EDIT
Although this answer has already been accepted, I want to post a code where the original assembly can not be modified.
var settings = new JsonSerializerSettings() {
ContractResolver = new AllPropertiesContractResolver()
};
reCaptchaResponse responseObject =
JsonConvert.DeserializeObject<reCaptchaResponse>(jsonResult ,settings);
public class AllPropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.Select(x => new Newtonsoft.Json.Serialization.JsonProperty()
{
PropertyName = x.Name,
PropertyType = x.PropertyType,
Readable = true,
ValueProvider = new AllPropertiesValueProvider(x),
Writable = true
})
.ToList();
return props;
}
}
public class AllPropertiesValueProvider : Newtonsoft.Json.Serialization.IValueProvider
{
PropertyInfo _propertyInfo;
public AllPropertiesValueProvider(PropertyInfo p)
{
_propertyInfo = p;
}
public object GetValue(object target)
{
return _propertyInfo.GetValue(target); //Serialization
}
public void SetValue(object target, object value)
{
_propertyInfo.SetValue(target, value, null); //Deserialization
}
}
Change the properties to public. By default it does not deserialize non-public properties
internal class reCaptchaResponse
{
public bool success { get; set; }
public DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
public string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
public string[] error_codes { get; set; } // optional
}
the members must be public
internal class reCaptchaResponse
{
public bool success { get; set; }
public DateTime challenge_ts { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
public string hostname { get; set; } // the hostname of the site where the reCAPTCHA was solved
public string[] error_codes { get; set; } // optional
}
You can try to change modificator from internal to public

How to deserialize JSON to C# class which uses composition

I need to deserialize
{'Id': 'id123',
'Time': 1436231503,
'Name': 'foo',
'ProductId': 1}
into Container1
public class Container1
{
public CommonFields Common { get; set; }
//fields specific to Container1
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "ProductId")]
public int ProductId { get; set; }
}
and
{
'Id': 'id123',
'Time': 1436231503,
'Group':'10768C21-9971-4D2F-ACD7-10C2EF19FCA8'
}
into Container2
public class Container2
{
public CommonFields Common { get; set; }
//fields specific to Container2
[JsonProperty(PropertyName = "Group")]
public Guid Group { get; set; }
}
using composition (not inheritance). Both JSON have 2 common fields (Id and Time) and specific fields.
With newtonsoft.json
JsonConvert.DeserializeObject<Container1>(json_container1)
the result is that the properties of the 2 container are correctly deserialized. The common properties of the composed classed are not deserialized.
How can I deserialize JSON into C# classes that use only composition?
(using newtonsoft.json is not compulsory)
Below is my attempt.
public class CommonFields
{
[JsonProperty(PropertyName = "Id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "Time")]
public long Time { get; set; }
}
public class Container1
{
public CommonFields Common { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "ProductId")]
public int ProductId { get; set; }
}
public class Container2
{
public CommonFields Common { get; set; }
[JsonProperty(PropertyName = "Group")]
public Guid Group { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
string json_container1 = #"{
'Id': 'id123',
'Time': 1436231503,
'Name': 'foo',
'ProductId': 1
}";
string json_container2 = #"{
'Id': 'id123',
'Time': 1436231503,
'Group':'10768C21-9971-4D2F-ACD7-10C2EF19FCA8'
}";
var container1Obj = JsonConvert.DeserializeObject<Container1>(json_container1);
var container2Obj = JsonConvert.DeserializeObject<Container2>(json_container2);
Console.ReadKey();
}}}
Don't do it.
The JSON element you deserilize from should not be change, you can remove some properties but it's a bad practice to change its properties structure.
JSON file\content should have a compatible JSON class, if you want to make any changes, make another custom class and make a mapping logic between them.
I think you can just do the deserialize again just on CommonFields of two objects.
container1Obj.Common = JsonConvert.DeserializeObject<CommonFields>(json_container1);
container2Obj.Common = JsonConvert.DeserializeObject<CommonFields>(json_container2);
I know you want to use composition, but I really can't see any pros using composition over inheritance here.
public class BaseClass
{
[JsonProperty(PropertyName = "Id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "Time")]
public long Time { get; set; }
}
public class Container1 : BaseClass
{
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "ProductId")]
public int ProductId { get; set; }
}
public class Container2 : BaseClass
{
[JsonProperty(PropertyName = "Group")]
public Guid Group { get; set; }
}
This is as simple as it can get and should get the job done.
Your question basically the reverse of the question Can I serialize nested properties to my class in one operation with Json.net?, and can be solved with a similar strategy. To simplify things, create an interface for all classes containing common fields:
public interface IHasCommonFields
{
CommonFields Common { get; set; }
}
Then you can create the following generic converter for any type implementing this interface:
public class HasCommonFieldsConverter<T> : JsonConverter where T : IHasCommonFields
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token == null || token.Type == JTokenType.Null)
return null;
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
{
var hasCommon = token.ToObject<T>(serializer);
var common = (hasCommon.Common ?? (hasCommon.Common = new CommonFields()));
serializer.Populate(token.CreateReader(), common);
return hasCommon;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
{
var hasCommon = (T)value;
var obj = JObject.FromObject(hasCommon, serializer);
var common = hasCommon.Common;
if (common != null)
{
var commonObj = JObject.FromObject(common, serializer);
obj.Merge(commonObj);
}
obj.WriteTo(writer);
}
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
Then apply it to all classes implementing IHasCommonFields - and in addition mark the Common property with JsonIgnore:
[JsonConverter(typeof(HasCommonFieldsConverter<Container1>))]
public class Container1 : IHasCommonFields
{
[JsonIgnore]
public CommonFields Common { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "ProductId")]
public int ProductId { get; set; }
}
[JsonConverter(typeof(HasCommonFieldsConverter<Container2>))]
public class Container2 : IHasCommonFields
{
[JsonIgnore]
public CommonFields Common { get; set; }
[JsonProperty(PropertyName = "Group")]
public Guid Group { get; set; }
}
Then, to test:
public class TestClass
{
public static void Test()
{
var container1 = new Container1 { Name = "name", ProductId = 101, Common = new CommonFields { Id = "1401", Time = DateTime.Today.Ticks } };
var container2 = new Container2 { Group = Guid.NewGuid(), Common = new CommonFields { Id = "2401", Time = DateTime.Today.Ticks } };
Test(container1);
Test(container2);
}
private static void Test<T>(T container) where T : class, IHasCommonFields
{
var json = JsonConvert.SerializeObject(container, Formatting.Indented);
Debug.WriteLine(json);
var containerback = JsonConvert.DeserializeObject<T>(json);
var json2 = JsonConvert.SerializeObject(containerback, Formatting.Indented);
Debug.Assert(json == json2); // No assert
if (container.Common != null)
{
Debug.Assert(container.Common.Id == containerback.Common.Id); // No assert
Debug.Assert(container.Common.Time == containerback.Common.Time); // No assert
}
}
}
The JSON created looks like:
{
"Name": "name",
"ProductId": 101,
"Id": "1401",
"Time": 635725152000000000
}
And
{
"Group": "9ed31118-c0b7-4d9f-8f57-303b2e164643",
"Id": "2401",
"Time": 635725152000000000
}
The one disadvantage of this converter is that, if the Common property is null when serialized, it will be deserialized non-null with default values.
If you don't want the IHasCommonFields interface, you could e.g. create an abstract generic base class for your converter with abstract methods to get and set the common fields, then override those methods in each subclass.
(Honestly inheritance does seem simpler than composition here, as other answers have stated.)

Categories