I need my web-api to return a list of Rule instances serialized in json format.
[HttpGet]
[SwaggerOperation(nameof(GetRules))]
[SwaggerResponse(StatusCodes.Status200OK, typeof(List<Rule>), "Rules")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> GetRules()
{
List<Rule> rules = /* retrieve rule from some storage */;
return Ok(rules);
}
For now, there are 2 kinds of rules, each with specific properties on top of the ones shared in the Rule class; one rule is called RuleWithExpiration and the other RuleWithGracePeriod.
[JsonObject(MemberSerialization.OptIn)]
public class Rule
{
[JsonProperty("id")]
public Guid Id { get; }
[JsonProperty("name")]
public string Name { get; }
[JsonConstructor]
public Rule(Guid id, string name)
{
Id = id;
Name = name;
}
}
[JsonObject(MemberSerialization.OptIn)]
public class RuleWithExpiration : Rule
{
[JsonProperty("someInfo")]
public string SomeInfo { get; }
[JsonProperty("expiration")]
DateTime Expiration { get; }
[JsonConstructor]
public RuleWithExpiration(Guid id, string name, string someInfo, DateTime expiration) : base(id, name)
{
SomeInfo = someInfo;
Expiration = expiration;
}
}
[JsonObject(MemberSerialization.OptIn)]
public class RuleWithGracePeriod : Rule
{
[JsonProperty("gracePeriod")]
public TimeSpan GracePeriod { get; }
[JsonConstructor]
public RuleWithGracePeriod(Guid id, string name, TimeSpan gracePeriod) : base(id, name)
{
GracePeriod = gracePeriod;
}
}
The problem I have is that this class hierarchy has issues when I try to deserialized it. After the deserialization, I end up with a list of Rule instances since I do not ask the serializer to include the type information, as it is considered a security issue.
void Main()
{
List<Rule> rules = new List<Rule>
{
new RuleWithExpiration(Guid.NewGuid(), "Rule with expiration", "Wat?", DateTime.UtcNow.AddHours(1d)),
new RuleWithGracePeriod(Guid.NewGuid(), "Rule with grace period", TimeSpan.FromHours(1d)),
};
var serializedRule = JsonConvert.SerializeObject(rules);
serializedRule.Dump();
List<Rule> deserializedRule = JsonConvert.DeserializeObject<List<Rule>>(serializedRule);
deserializedRule.Dump();
}
Here is the serialized string:
[{"someInfo":"Wat?","expiration":"2018-07-26T13:32:06.2287669Z","id":"29fa0603-c103-4a95-b627-0097619a7645","name":"Rule with expiration"},{"gracePeriod":"01:00:00","id":"bd8777bb-c6b3-4172-916a-546775062eb1","name":"Rule with grace period"}]
And here is the list of Rule instances I get after it is deserialized (as shown in LINQPad):
Question
Is it possible to keep this inheritance tree in this context or do I have to rearrange these classes somehow? If so, what would be the way to do this?
Solution
I have not found solutions that felt good.
For instance, I could have some
RuleAggregate class like this one, but everytime I introduce a new kind of rule, I have to edit this class and deal with the impact:
[JsonObject(MemberSerialization.OptIn)]
public class RuleAggregate
{
[JsonProperty("expirations")]
public List<RuleWithExpiration> Expirations {get;}
[JsonProperty("gracePeriods")]
public List<RuleWithGracePeriod> GracePeriods {get;}
[JsonConstructor]
public RuleAggregate(List<RuleWithExpiration> expirations, List<RuleWithGracePeriod> gracePeriods)
{
Expirations = expirations;
GracePeriods = gracePeriods;
}
}
The solution I found with the less trade-offs -if I want to keep the inheritance tree- is to fall back on the good ol' XML serialization.
Ok, right, plain TypeNameHandling.All makes it vulnerable. What about this approach?
void Main()
{
Stockholder stockholder = new Stockholder
{
FullName = "Steve Stockholder",
Businesses = new List<Business>
{
new Hotel
{
Name = "Hudson Hotel",
Stars = 4
}
}
};
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = new KnownTypesBinder { KnownTypes = new List<Type> { typeof(Stockholder), typeof(Hotel) }}
};
string ok;
/*
ok = JsonConvert.SerializeObject(stockholder, Newtonsoft.Json.Formatting.Indented, settings);
Console.WriteLine(ok);*/
ok = #"{
""$type"": ""Stockholder"",
""FullName"": ""Steve Stockholder"",
""Businesses"": [
{
""$type"": ""Hotel"",
""Stars"": 4,
""Name"": ""Hudson Hotel""
}
]
}";
JsonConvert.DeserializeObject<Stockholder>(ok, settings).Dump();
var vector = #"{
""$type"": ""Stockholder"",
""FullName"": ""Steve Stockholder"",
""Businesses"": [
{
""$type"": ""System.IO.FileInfo, System.IO.FileSystem"",
""fileName"": ""d:\rce-test.txt"",
""IsReadOnly"": true
}
]
}";
JsonConvert.DeserializeObject<Stockholder>(vector, settings).Dump(); // will fail
}
public class KnownTypesBinder : ISerializationBinder
{
public IList<Type> KnownTypes { get; set; }
public Type BindToType(string assemblyName, string typeName)
{
return KnownTypes.SingleOrDefault(t => t.Name == typeName);
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.Name;
}
}
public abstract class Business
{
public string Name { get; set; }
}
public class Hotel: Business
{
public int Stars { get; set; }
}
public class Stockholder
{
public string FullName { get; set; }
public IList<Business> Businesses { get; set; }
}
Related
I have a class mapping like this:
public class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("content")]
public ContentStructure Content { get; set; }
}
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string #string) => new ContentStructure { ContentString = #string };
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
When I attempt to deserialize the following JSON string:
{
"id": "any_id",
"type": "any_type",
"content": {
"id": "any_id",
"duration": 1000
}
}
I always get the deserialized settings object with the property settings.Content.ContentClass null, but whenever my JSON string has the property "content" as string (instead of an object) the structure field ContentString comes correctly. What I am doing wrong? How can I can convert the JSON string above correctly?
Yet another solution could be to make use of the JsonSchema
First let's redefine your data model:
public abstract class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class SettingsV1 : Settings
{
[JsonProperty("content")]
public string Content { get; set; }
}
public class SettingsV2 : Settings
{
[JsonProperty("content")]
public Content Content { get; set; }
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
Instead of having a middle man (ContentStructure) rather than you can have two separate Settings versions
The common fields are defined inside the abstract base class
Now, you can use these two versioned classes to define json schemas:
private static JSchema schemaV1;
private static JSchema schemaV2;
//...
var generator = new JSchemaGenerator();
schemaV1 = generator.Generate(typeof(SettingsV1));
schemaV2 = generator.Generate(typeof(SettingsV2));
Finally all you need to do is to do a preliminary check before calling the DeserializeObject with the proper type:
Settings settings = null;
var semiParsed = JObject.Parse(json);
if (semiParsed.IsValid(schemaV1))
{
settings = JsonConvert.DeserializeObject<SettingsV1>(json);
}
else if (semiParsed.IsValid(schemaV2))
{
settings = JsonConvert.DeserializeObject<SettingsV2>(json);
}
else
{
throw new NotSupportedException("The provided json format is not supported");
}
Use a Custom JsonConverter. Modify it based on your need.
[JsonConverter(typeof(ContentStructureConverter))]
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string #string) => new ContentStructure { ContentString = #string };
}
public class ContentStructureConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ContentStructure);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ContentStructure contentStruct;
if (reader.ValueType == typeof(string))
contentStruct = reader.Value as string;
else
contentStruct = serializer.Deserialize<Content>(reader);
return contentStruct;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ContentStructure? contentStruct = value as ContentStructure?;
if (contentStruct.HasValue && contentStruct.Value.ContentClass != null)
serializer.Serialize(writer, contentStruct.Value.ContentClass);
else
serializer.Serialize(writer, contentStruct.Value.ContentString);
}
}
The input json is wrong for your format.
If you use this json instead,
{
"id": "any_id",
"type": "any_type",
"content": {
"ContentClass" : {
"id": "any_id",
"duration": 1000
}
}
}
This way, it will work.
settings.Content.ContentClass is three layer but your is json two layer (settings.Content). So after "content", it is looking for id and duration which ContentStructure does not have these two fields.
My reasoning is when it encounters a value type (like {"field" : "value}), it will look for value type, like string, int or double. When it encounters a json type (like {"field" : {another json here} }), it will look for class or struct.
I would like to know how to read the JSON with a dynamic layout into my object that has an interface IPeople. When I try I get an error:
'Could not create an instance of type 'xxxx'. Type is an interface or abstract class and cannot be instantiated.
I have spent 2 days searching through Google and I tried using converters and it only works for one object at a time So I would have to choose between a Staff converter and a spectator converter.
How can I get both objects to load using the interface IPeople that loads to the Staff object if the staff data is detected and Spectator object if the spectator data is detected (possibly more object types all have unique properties).
{
"appCore":
{
"Crowd":
[
{
"Name": "Name1",
"Staff":
{
"PopupText":
[
{ "DisplayName":"No Popup Text","PopupValue":"", "IsSeperator":false},
{ "DisplayName":"--------------","PopupValue":"", "IsSeperator":true},
{ "DisplayName":"HT","PopupValue":"HT", "IsSeperator":false}
]
},
"Spectator":
{
"PopupText2":
[
{ "DisplayName":"No Popup Text","PopupValue":"", "Cheese":"hiih"},
{ "DisplayName":"--------------","PopupValue":"", "Cheese":"hiih"},
{ "DisplayName":"Half-Time","PopupValue":"Half-Time", "Cheese":"hiih"}
]
}
}
]
}
}
C# Models:
public class Crowd : ICrowd
{
public IPeople People { get; set; }
}
public class Staff : IPeople
{
IList<PopupTextData> PopupText { get; set; }
}
public class Spectator : IPeople
{
IList<PopupTextData2> PopupText2 { get; set; }
}
public class PopupTextData
{
public string DisplayName { get; set; }
public string PopupValue { get; set; }
public bool IsSeperator { get; set; }
}
public class PopupTextData2
{
public string DisplayName { get; set; }
public string PopupValue { get; set; }
public string Cheese { get; set; }
}
Code used to read the data:
settings.ForEach(type =>
{
builder.Register(c => c.Resolve<MCoreReader>().LoadSection(type))
.As(type.GetInterfaces())
.AsImplementedInterfaces()
.InstancePerRequest()
.InstancePerLifetimeScope();
});
public static object LoadSection(Type type, string _configurationFilePath, string _sectionNameSuffix)
{
if (!File.Exists(_configurationFilePath))
return Activator.CreateInstance(type);
var jsonFile = File.ReadAllText(_configurationFilePath);
var section = ToCamelCase(type.Name.Replace(_sectionNameSuffix, string.Empty));
var settingsData = JsonConvert.DeserializeObject<dynamic>(jsonFile, JsonSerializerSettings);
var settingsSection = settingsData[section];
return settingsSection == null
? Activator.CreateInstance(type)
: JsonConvert.DeserializeObject(JsonConvert.SerializeObject(settingsSection), type, JsonSerializerSettings);
}
private class SettingsReaderContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p =>
{
p.Writable = true;
p.Readable = true;
});
return props;
}
}
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = new SettingsReaderContractResolver(),
};
Assuming you control the Json, here's a working example: I've changed the class structure a bit.
It uses TypeNameHandling.All which writes the type information into the Json - as $type -, so when it's deserialized (using the same setting), it can restore the type.
void Main() {
var crowd = new Crowd {
People = new List<IPeople> {
new Staff {
PopupText = new List<PopupTextData> {
new PopupTextData { DisplayName = "Staff"}
}},
new Spectator {
PopupText = new List<PopupTextData> {
new PopupTextData { DisplayName = "Spectator"}
}},
}
};
var settings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};
var json = JsonConvert.SerializeObject(crowd, settings);
var newCrowd = JsonConvert.DeserializeObject<ICrowd>(json, settings);
Console.WriteLine(newCrowd.People.Count); // outputs '2'
}
public interface ICrowd {
IList<IPeople> People { get; set; }
}
public interface IPeople {
IList<PopupTextData> PopupText { get; set; }
}
public class Crowd : ICrowd {
public IList<IPeople> People { get; set; }
}
public class Staff : IPeople {
public IList<PopupTextData> PopupText { get; set; }
}
public class Spectator : IPeople {
public IList<PopupTextData> PopupText { get; set; }
}
public class PopupTextData {
public string DisplayName { get; set; }
public string PopupValue { get; set; }
public bool IsSeperator { get; set; }
}
I have 3 model types:
public class BankA_Transaction : BanKTransactionMetaData
{
public string GROUPName { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
public class BankB_Transaction : BanKTransactionMetaData
{
public string Name { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
public class BankC_Transaction : BanKTransactionMetaData
{
public string FullName { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
Note: The actual property lists are much longer
All of which inherit some fields needed when saving into the database.
public class BanKTransactionMetaData
{
public String BankName { get; set; }
}
These models get filled with records from a file sent by the bank and then saved to a database.
As part of this save I convert the records to JSON as that is required by the database.
public void SaveBankA(BankA bankA)
{
bankA.BankName = "Bank_A";
string jsonText = JsonConvert.SerializeObject(bankA_Transaction, Formatting.Indented);
Code for saving...
At the moment I have a different methods for SaveBankA, SaveBankA and SaveBankB.
It seems to me that this is code replication and that I should get all the models to inherit better in order to use a base type? instead of each named type.
I've read up on Abstract and Virtual classes as I suspect that's what I need but I can't work out how to plug it together.
I can't just use Object in SaveBankA as I need to add .BankName.
Is there a better architecture to reduce code replication?
Perhaps you need something like this?
In base service class:
protected void SaveBankTransaction(BankTransactionMetaData tran)
{
string jsonText = JsonConvert.SerializeObject(tran, Formatting.Indented);
// additional saving code
}
In child service classes:
public void SaveBankA(BankA bankA)
{
bankA.BankName = "Bank_A";
base.SaveBankTransaction(bankA);
}
Create a couple of interfaces, one for your meta data (IBankData) and one for your bank transaction details (IBankTransaction). The IBankData interface will maintain a reference to the IBankTransaction interface. This should also allow you to add additional banks when needed, e.g. Bank D.
public interface IBankData
{
string BankName { get; }
// ... additional bank meta data properties
// ...
IBankTransaction Transaction { get; set; }
}
public interface IBankTransaction
{
[JsonProperty("ACC_ID")]
string AccountId { get; set; }
[JsonProperty("ACCOUNT_NO")]
string AccountNumber { get; set; }
// ... additional shared bank transaction properties
// ...
}
FYI, I chose to use the JsonProperty attribute to control the name for the JSON key, this allows the class properties to be named according to best practices without affecting the JSON property names.
Next implement the interfaces for each bank you will be working with. In each bank add the additional properties that will only apply to each implementation, i.e. since the GroupName property is only used by BankA, this property will be added to the BankA class and not the interface. The same goes for any other bank specific properties.
Bank A
public class BankA : IBankData
{
public string BankName => "BankA";
public IBankTransaction Transaction { get; set; }
}
public class BankATransaction : IBankTransaction
{
// Bank A specific properties
[JsonProperty("GROUPName")]
public string GroupName { get; set; }
// ... additional Bank A specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
Bank B
public class BankB : IBankData
{
public string BankName => "BankB";
public IBankTransaction Transaction { get; set; }
}
public class BankBTransaction : IBankTransaction
{
// Bank B specific properties
public string Name { get; set; }
// ... additional Bank B specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
Bank C
public class BankC : IBankData
{
public string BankName => "BankC";
public IBankTransaction Transaction { get; set; }
}
public class BankCTransaction : IBankTransaction
{
// Bank B specific properties
public string FullName { get; set; }
// ... additional Bank B specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
JsonConverter
Since the IBankTransaction is a property within the IBankData this will change your JSON structer. You may not want this, to retain your structure, a JsonConverter can be implemented on the IBankData interface. This will remove the Transaction object in the JSON and move the child properties under the JSON root.
public class BankJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
JProperty transactionProperty = o.Properties().FirstOrDefault(p => p.Name == "Transaction");
o.Remove("Transaction");
JToken token = transactionProperty;
foreach (JToken ct in token.Children())
{
foreach (var prop in JProperty.FromObject(ct))
{
o.Add(prop);
}
}
serializer.Serialize(writer, o);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return objectType.GetInterfaces().Contains(typeof(IBankData));
}
}
Usage
For the usage example I've created a few test functions to prep the data and added SaveBank method that you can relocate in your actual code as it would make sense for your solution.
class Program
{
static void Main(string[] args)
{
string bankATransJson = GetBankATestJsonInput();
BankATransaction bankATransaction = JsonConvert.DeserializeObject<BankATransaction>(bankATransJson);
BankA bankA = new BankA();
bankA.Transaction = bankATransaction;
Console.WriteLine(SaveBank(bankA));
// output:
// {
// "BankName": "BankA",
// "GROUPName": "g54321",
// "ACC_ID": "A01",
// "ACCOUNT_NO": "A1111"
// }
string bankBInputJson = GetBankBTestJsonInput();
BankBTransaction bankBTransInput = JsonConvert.DeserializeObject<BankBTransaction>(bankBInputJson);
BankB bankB = new BankB();
bankB.Transaction = bankBTransInput;
Console.WriteLine(SaveBank(bankB));
// output:
// {
// "BankName": "BankB",
// "ACC_ID": "B02",
// "ACCOUNT_NO": "B2222",
// "Name": "Bank_Of_B
// }
string bankCInputJson = GetBankCTestJsonInput();
BankCTransaction bankCTransInput = JsonConvert.DeserializeObject<BankCTransaction>(bankCInputJson);
BankC bankC = new BankC();
bankC.Transaction = bankCTransInput;
Console.WriteLine(SaveBank(bankC));
// output:
// {
// "BankName": "BankC",
// "ACC_ID": "C03",
// "ACCOUNT_NO": "C3333",
// "FullName": "C Bank"
// }
}
public static string SaveBank(IBankData bankData)
{
// when calling the serialize object method, we pass our BankJsonConverter
string jsonText = JsonConvert.SerializeObject(bankData, Formatting.Indented, new BankJsonConverter());
// this example just returns the JSON text
// but you would implement your save logic as needed
return jsonText;
}
private static string GetBankATestJsonInput()
{
var obj = new { ACC_ID = "A01", ACCOUNT_NO = "A1111", GROUPName = "g54321" };
return JsonConvert.SerializeObject(obj);
}
private static string GetBankBTestJsonInput()
{
var obj = new { ACC_ID = "B02", ACCOUNT_NO = "B2222", Name = "Bank_Of_B" };
return JsonConvert.SerializeObject(obj);
}
private static string GetBankCTestJsonInput()
{
var obj = new { ACC_ID = "C03", ACCOUNT_NO = "C3333", FullName = "C Bank" };
return JsonConvert.SerializeObject(obj);
}
}
Small intro to explain the context:
On Server side:
I have an assembly "TheoreticalObjects"
this assembly contains a base class "BaseClass" from which all my THEORETICAL objects are derivating
the classes inheriting from TheoreticalObjects.BaseClass are for example:
TheoreticalObjects.Tube, TheoreticalObjects.Flange, TheoreticalObjects.Caps...
On Client side:
I have also that assembly for theoretical objects
but I have an other assembly (dedicated to generate vertices) called "RealObjects"
this assembly contains a base class "BaseClass" from which all my REAL objects are derivating
the classes inheriting from RealObjects.BaseClass are for example:
RealObjects.Tube, RealObjects.Flange, RealObjects.Caps...
I want to serialize my objects on the server (as TheoreticalObjects.BaseClass), send the json by tcp to the client, and deserialize the json (as RealObjects.BaseClass).
Here are my classes: (Let's suppose that we want to create a Tube):
// my TheoreticalObjects.BaseClass
namespace TheoreticalObjects
{
[DataContract]
public class BaseClass
{
[DataMember]
public Guid GUID { get; set; }
[DataMember]
public int ID { get; set; }
[DataMember]
public string Designation { get; set; }
[DataMember]
public string Product { get; set; }
[DataMember]
public int IDMaterial { get; set; }
[DataMember]
public int Quantity { get; set; }
[DataMember]
public string Form { get; set; }
protected BaseClass()
{ }
protected BaseClass(int iD, string designation, string product, int iDMaterial, int quantity, string form)
{
ID = iD;
Designation = designation;
Product = product;
IDMaterial = iDMaterial;
Quantity = quantity;
Form = form;
}
}
}
// my TheoreticalObjects.Tube
namespace TheoreticalObjects
{
[DataContract]
public class Tube : BaseClass
{
[DataMember]
public Length Diameter { get; set; }
[DataMember]
public Length WallThickness { get; set; }
[DataMember]
public Length Length { get; set; }
public Tube() : base()
{ }
public Tube(int iD, string designation, string product, int iDmaterial, int quantity, string form, Length diameter, Length Wallthickness, Length length) : base(iD, designation, product, iDmaterial, quantity,form)
{
WallThickness = Wallthickness;
Diameter = diameter;
Length = length;
}
}
}
// my RealObjects.BaseClass
namespace RealObjects
{
public class BaseClass
{
public Guid GUID { get; set; }
public int ID { get; set; }
public string Designation { get; set; }
public string Product { get; set; }
public int IDMaterial { get; set; }
public int Quantity { get; set; }
public string Form { get; set; }
protected BaseClass() { }
protected BaseClass(int iD, string designation, string product, int iDMaterial, int quantity, string form)
{
ID = iD;
Designation = designation;
Product = product;
IDMaterial = iDMaterial;
Quantity = quantity;
Form = form;
}
public List<Face> myFaces = new List<Face>(); // faces of the mesh
public MyMesh mesh = new MyMesh();
public void Triangulation(TopoDS_Shape shape, double deflection)
{
// things ...
myFaces = things...
mesh = new MyMesh(myFaces);
}
}
}
// my RealObjects.Tube
namespace RealObjects
{
public class Tube: BaseClass
{
public double diameter;
public double Wallthickness;
public double length;
public Tube() : base() { }
public Tube(int iD, string designation, string product, int iDmaterial, int quantity, string form, double diameter, double wallThickness, double length) : base(iD, designation, product, iDmaterial, quantity, form)
{
this.diameter = diameter;
this.Wallthickness = wallThickness;
this.length = length;
Build(diameter, Wallthickness, length);
}
public void Build(double diameter, double Wallthickness, double length)
{
//things ...
Triangulation(things...);
}
}
}
My problem is that after serializing and sending my Tube to the client, it doesn't deserialize correctly: I get a RealObjects.BaseClass instead of a RealObjects.BaseClass.Tube.
I did a Binder to bind names to types, but BindToType() isn't getting called at all when deserializing
_______________On server side____________
//creating the Tube
TheoreticalObjects.Tube c = new TheoreticalObjects.Tube(1, "Tube", "Element", 1, 1, "tube", new Length(1, UnitsNet.Units.LengthUnit.Meter), new Length(0.1, UnitsNet.Units.LengthUnit.Meter), new Length(2, UnitsNet.Units.LengthUnit.Meter));
// settings for the serializer
JsonSerializerSettings _jsonSerializerSettingsOCCServer = new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented };
_jsonSerializerSettingsOCCServer.Converters.Add(new UnitsNetJsonConverter());
// serialization
string json = JsonConvert.SerializeObject(c, _jsonSerializerSettingsOCCServer).Replace("\r\n", "\n");
// the message that the server will send
CommunicateElement messageObject = new CommunicateElement(NetworkComms.NetworkIdentifier, json, 1234, c.Designation);
after that the message is sent
_______________On client side____________
the message is handled, a function put the message in a "constructionQueue"
// settings for the deserializer
_jsonSerializerSettingsOCC = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All,
Binder = new MyBinder(),
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
Formatting = Newtonsoft.Json.Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
_jsonSerializerSettingsOCC.Converters.Add(new UnitsNetJsonConverter());
// deserialize the json (that was previously a TheoreticalObjects.Tube) into a RealObjects.BaseClass and add it to the construction queue
constructionQueue.Add(JsonConvert.DeserializeObject<RealObjects.BaseClass>(messageObject.Message, _jsonSerializerSettingsOCC));
......
...... once it is in the construction queue, I try creating it .....
...... no need to know what happens after that .....
...... note that i'm looking for a RealObjects.Tube and not a RealObjects.BaseClass .....
......
_______________ MyBinder ____________
public class MyBinder : SerializationBinder
{
readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>();
readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public MyBinder()
{
List<Type> myTypes = new List<Type>();
Assembly[] myAssemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < myAssemblies.Length; i++)
{
if (myAssemblies[i].GetName().Name == "RealObjects")
{
foreach (Type t in myAssemblies[i].GetTypes())
{
if (t.IsSubclassOf(typeof(RealObjects.BaseClass)))
{
myTypes.Add(t);
}
}
break;
}
}
foreach (var type in myTypes)
{
Map(type, type.Name);
}
}
public void Map(Type type, string name)
{
this.typeToName.Add(type, name);
this.nameToType.Add(name, type);
}
public Type Get(string typeName)
{
return nameToType[typeName];
}
public string Get(Type type)
{
return typeToName[type];
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
// we retrieve the name in the RealObjects assembly
typeName = Get(serializedType);
assemblyName = "RealObjects";
}
public override Type BindToType(string assemblyName, string typeName)
{
return Get(typeName);
}
} // credit: https://stackoverflow.com/questions/11099466/using-a-custom-type-discriminator-to-tell-json-net-which-type-of-a-class-hierarc
I wasn't able to call the constructor because my RealObjects.BaseClass doesn't have the fields Diameter, wallThickness and Length that my RealObjects.Tube does have, and I loose their values when deserializing to RealObjects.BaseClass
// called after being added to the construction queue
private void CreateObject(RealObjects.BaseClass c)
{
MyBinder binder = new MyBinder();
Type type = binder.BindToType("RealObjects", c.Designation);
ConstructorInfo[] ctor = type.GetConstructors();
BasicClass be;
foreach (ConstructorInfo ci in ctor)
{
try
{
object instance = ci.Invoke(new object[] { c });
be = (BasicClass )instance;
} catch (Exception e)
{
Debug.Log(e.ToString());
}
}
// things...
}
All suggestions are open
I hope my english wasn't too bad, and that I explained myself clearly, thank you for any help
The missing part of the puzzle was the way of serializing that was missing the "polymorphic type information for the root object", which needed to be added as shown in this answer to Serializing an interface/abstract object using NewtonSoft.JSON by dbc:
TheoreticalObjects.Tube c = new TheoreticalObjects.Tube(1, "Tube", "Element", 1, 1, "tube", new Length(1, UnitsNet.Units.LengthUnit.Meter), new Length(0.1, UnitsNet.Units.LengthUnit.Meter), new Length(2, UnitsNet.Units.LengthUnit.Meter));
JsonSerializerSettings _jsonSerializerSettingsOCCServer = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Formatting = Newtonsoft.Json.Formatting.Indented };
_jsonSerializerSettingsOCCServer.Converters.Add(new UnitsNetJsonConverter());
string json = JsonConvert.SerializeObject(c, typeof(TheoreticalObjects.BaseClass), _jsonSerializerSettingsOCCServer);
The settings for the deserialization part remains the same as on my first post.
But the constructor of the binder changes:
public MyBinder()
{
List<Type> myTypes = new List<Type>();
Assembly[] myAssemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < myAssemblies.Length; i++)
{
if (myAssemblies[i].GetName().Name == "RealObjects")
{
foreach (Type t in myAssemblies[i].GetTypes())
{
if (t.IsSubclassOf(typeof(RealObjects.BaseClass)))
{
myTypes.Add(t);
}
}
break;
}
}
foreach (var type in myTypes)
{
Map(type, "TheoreticalObjects."+type.Name); //this part changed
}
}
that way you bound to the right class (RealObjects.Tube)
I was having an instance of the class RealObjects.Tube in my construction queue
Note that I had to modify the fields in my RealObjects.Tube to be of Length type and not double type
Is it possible to serialize static properties with JSON.NET without adding [JsonProperty] attribute to each property.
Example class:
public class Settings
{
public static int IntSetting { get; set; }
public static string StrSetting { get; set; }
static Settings()
{
IntSetting = 5;
StrSetting = "Test str";
}
}
Expected result:
{
"IntSetting": 5,
"StrSetting": "Test str"
}
Default behavior skips static properties:
var x = JsonConvert.SerializeObject(new Settings(), Formatting.Indented);
You can do this with a custom contract resolver. Specifically you need to subclass DefaultContractResolver and override the GetSerializableMembers function:
public class StaticPropertyContractResolver : DefaultContractResolver
{
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
var baseMembers = base.GetSerializableMembers(objectType);
PropertyInfo[] staticMembers =
objectType.GetProperties(BindingFlags.Static | BindingFlags.Public);
baseMembers.AddRange(staticMembers);
return baseMembers;
}
}
Here all we're doing is calling the base implementation of GetSerializableMembers, then adding public static properties to our list of members to serialize.
To use it you can create a new JsonSerializerSettings object and set the ContractResolver to an instance of the StaticPropertyContractResolver:
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = new StaticPropertyContractResolver();
Now, pass those settings to JsonConvert.SerializeObject and everything should work:
string json = JsonConvert.SerializeObject(new Settings(), serializerSettings);
Output:
{
"IntSetting": 5,
"StrSetting": "Test str"
}
Example: https://dotnetfiddle.net/pswTJW
A more complicated way to solve this:
Solution 1:
public class Settings
{
int intsetting { get; set; } /*= 0;*/ // commented only allowed in C# 6+
string strsetting { get; set; } /*= "";*/
public int IntSetting { get { return intsetting; } set { intsetting = value; } }
public string StrSetting { get { return strsetting; } set { strsetting = value; } }
static Settings()
{
IntSetting = 5;
StrSetting = "Test str";
}
}
Solution 2: (less complicated)
public class Settings
{
[JsonProperty]
public static int IntSetting { get; set; }
[JsonProperty]
public static string StrSetting { get; set; }
static Settings()
{
IntSetting = 5;
StrSetting = "Test str";
}
}
Adding the [JsonProperty] to all variables would be the easyest way of solving this, but when you don't want to use it Solution 1 would fit best for you.