I'm not being able to deserialize a collection of elements where the instances have a Inheritance relationship between them.
Does anyone came across this issue?
So my use case is this:
My model is similiar to this:
[DataContract]
public class Item
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public bool Valid { get; set; }
}
[DataContract]
public class IntermediateItem : Item
{
[DataMember]
public int Priority { get; set; }
}
[DataContract]
public class ExtendedItem : IntermediateItem
{
[DataMember]
public int Count { get; set; }
[DataMember]
public ItemsCollection Childs { get; set; }
}
And Items Collection is something like this:
[DataContract]
public class ItemsCollection : Collection<Item>
{
}
The setup that I have made to ensure the proper deserialization is:
Defining the CollectionFormatterBase:
public class ItemCollectionFormatterBase : CollectionFormatterBase<Item, ItemsCollection>
{
protected override ItemsCollection Create(int count)
{
return new ItemsCollection();
}
protected override void Add(ItemsCollection collection, int index, Item value)
{
collection.Add(value);
}
}
The example that is not working, and not working I mean, the deserialized instances are all of base type, some how the inheritance relationship got lost in the serialization.
Example:
MessagePack.Resolvers.CompositeResolver.RegisterAndSetAsDefault(new[] { new ItemCollectionFormatterBase() }, new[] { StandardResolver.Instance });
ExtendedItem instance = new ExtendedItem()
{
Id = 1,
Name = "Extended Item",
Priority = 121,
Valid = true,
Count = 10,
Childs = new ItemsCollection(new List<Item>() { new Item() { Id = 1 }, new IntermediateItem() { Priority = 10 }, new ExtendedItem() { Count = 10 } })
};
byte[] bytes = MessagePackSerializer.Serialize(instance);
using (FileStream file = new FileStream(this.filePath.AbsolutePath, FileMode.Create))
{
await file.WriteAsync(bytes , 0, payload.Length);
await file.FlushAsync();
}
using (FileStream file = new FileStream(testsFolder + #"\ExtendedItem.msgPack-csharp.dat", FileMode.Open))
{
file.Seek(0, SeekOrigin.Begin);
deserializedInstance = MessagePackSerializer.Deserialize<ExtendedItem>(file);
}
looking at the deserializedInstance Childs elements they all are from Item Type.
Can you tell me what I'm doing wrong ? What is missing ?
A small update regarding Item definition:
[DataContract]
[KnownType(typeof(IntermediateItem))]
[KnownType(typeof(ExtendedItem))]
public class Item
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public bool Valid { get; set; }
}
This also does not work. :(
Well looks like MessagePackSerializer static type as a static inner class called Typeless and that solve my problem:
With a instance of a ExtendedItem:
ExtendedItem instance = new ExtendedItem()
{
Id = 1,
Name = "Extended Item",
Priority = 121,
Valid = true,
Count = 10,
Childs = new ItemsCollection(new List<Item>() { new Item() { Id = 1 }, new IntermediateItem() { Priority = 10 }, new ExtendedItem() { Count = 10 } })
};
I was able to serialize that and deserialize with success !
byte[] bytes = MessagePackSerializer.Typeless.Serialize(instance);
await fileManager.WriteAsync(bytes);
ExtendedItem deserializedInstance = null;
deserializedInstance = MessagePackSerializer.Typeless.Deserialize(bytes) as ExtendedItem;
despite the serialization and deserialization worked on .NET this sample did not work when deserializing in nodejs with msgpackjs package.
Related
I have class object and I need to store its values and keys in specific format.
public class AppSettings
{
public int TokenLifeTime { get; set; } = 450;
public List<string> Urls { get; set; } = new List<string>
{
"www.google.com",
"www.hotmail.com"
};
public List<ServersList> ServersList { get; set; } = new List<ServersList>
{
new ServersList {IsHttpsAllowed = false},
new ServersList {IsHttpsAllowed = true}
};
}
public class ServersList
{
public bool IsHttpsAllowed { get; set; }
}
I want to get keys in this format.
"AppSettings:TokenLifeTime" , 450
"AppSettings:Urls:0", "www.google.com"
"AppSettings:Urls:1", "www.hotmail.com"
"AppSettings:ServersList:0:IsHttpsAllowed", false
"AppSettings:ServersList:1:IsHttpsAllowed", true
Is there any way to get all keys as string recursively regardless of object depths. Above code is just an example in real case I have long list and lot more data.
I don't think that there is anything out of the box for this.
You would need to create something yourself and define your rules.
In its more primitive form, I'd start with this:
Type t = typeof(AppSettings);
Console.WriteLine("The {0} type has the following properties: ",
t.Name);
foreach (var prop in t.GetProperties())
Console.WriteLine(" {0} ({1})", prop.Name,
prop.PropertyType.Name);
Then add a rule for IEnumerable to handle them in iterations and so forth for objects and primitive value types.
I have a couple of examples for you:
Option 1:
public class AppSettings
{
public int TokenLifeTime { get; set; } = 450;
public Dictionary<string, ServersList> Urls { get; set; } = new Dictionary<string, ServersList>
{
{"www.google.com", new ServersList {IsHttpsAllowed = false}},
{ "www.hotmail.com", new ServersList {IsHttpsAllowed = true}}
};
}
public class ServersList
{
public bool IsHttpsAllowed { get; set; }
}
This option would group the values together but you would lose 'int' based index. Not sure if that is important.
Option 2:
public class AppSettings
{
public int TokenLifeTime { get; set; } = 450;
public List<KeyValuePair<string, ServersList>> Urls { get; set; } = new List<KeyValuePair<string, ServersList>>
{
new KeyValuePair<string, ServersList>("www.google.com",new ServersList {IsHttpsAllowed = false}),
new KeyValuePair<string, ServersList>("www.hotmail.com", new ServersList {IsHttpsAllowed = true})
};
public List<ServersList> ServersList { get; set; } = new List<ServersList>
{
new ServersList {IsHttpsAllowed = false},
new ServersList {IsHttpsAllowed = true}
};
}
public class ServersList
{
public bool IsHttpsAllowed { get; set; }
}
This option will retain 'int' based indexing and the values are still grouped. But feels clunky...
Option 3: (the one I would go with)
public class AppSettings
{
public int TokenLifeTime { get; set; } = 450;
public List<Server> ServersList { get; set; } = new List<Server>
{
new Server { Url = "www.google.com", IsHttpsAllowed = false},
new Server { Url = "www.hotmail.com", IsHttpsAllowed = true}
};
}
public class Server
{
public string Url { get; set; }
public bool IsHttpsAllowed { get; set; }
}
This option still gives you 'int' based indexing and it groups the data together (as it should be from what I understand in the example).
I am trying, but failing to access the hits["_index"], hits["_type"], hits["_id"], hits["_score"] and hits["_source"] from the below mentioned dictionary to load into a db.
Trying to access every key-val pair below:
{
"took" : 12,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
**"hits" : {
"total" : 2700881,
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_index",
"_type" : "doc",
"_id" : "R22224!!5333e7e4-9ee3-45f4-9dc3-2a8b8d8cdcf8",
"_score" : 1.0,
"_source" : {
"duration" : 14986283,
"group_id" : "com",
"var_time" : "2018-04-24T17:05:13.082+02:00",
"var_name" : "2",
}
}
]
}**
}
Things I tried:
Tried the below c# code and reviewed following urls:
Unable to fetch _source dictionary key-val from elastic client search response
https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/auto-map.html
public class HitsDocument
{
[PropertyName("_index")]
public string Hitsindex { get; set; }
[PropertyName("_type")]
public string Hitstype { get; set; }
[PropertyName("_id")]
public string Hitsid { get; set; }
[PropertyName("_score")]
public string Hitsscore { get; set; }
[PropertyName("_source")]
public RawDocument Hitssource { get; set; }
}
public class RawDocument
{
[PropertyName("duration")]
public long Duration { get; set; }
[PropertyName("group_id")]
public string GroupId { get; set; }
[PropertyName("var_time")]
public DateTime Vartime { get; set; }
[PropertyName("var_name")]
public string Varname { get; set; }
}
static void Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool)
.DefaultMappingFor<HitsDocument>(m => m
.IndexName("test_index")
.TypeName("doc"));
var searchResponse = client.Search<HitsDocument>();
var numberOfSlices = 4;
var scrollAllObservable = client.ScrollAll<HitsDocument>("3m", numberOfSlices)
.Wait(TimeSpan.FromMinutes(5), onNext: s =>
{
var docs = s.SearchResponse.DebugInformation;
var documents = s.SearchResponse.Hits;
foreach (var document in documents)
{
// do something with this set of documents
// business logic to load into the database.
MessageBox.Show("document.Id=" + document.Id);
MessageBox.Show("document.Score=" + document.Score);
MessageBox.Show("document.Source=" + document.Source);
MessageBox.Show("document.Type=" + document.Type);
MessageBox.Show("document.Index=" + document.Index);
}
});
}
What am I doing wrong and also please point me in direction of documentation that betters my understanding of the API client for nested dictionary?
Thanks in advance.
Update:
The following code solution originally answered by #Russ Cam in this link answers it.
I failed to realize it before.
public class RawDocument
{
[PropertyName("duration")]
public long Duration { get; set; }
[PropertyName("group_id")]
public string GroupId { get; set; }
[PropertyName("var_time")]
public DateTime Vartime { get; set; }
[PropertyName("var_name")]
public string Varname { get; set; }
}
static void Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool)
.DefaultMappingFor<RawDocument>(m => m
.IndexName("test_index")
.TypeName("doc"));
var searchResponse = client.Search<RawDocument>();
var numberOfSlices = 4;
var scrollAllObservable = client.ScrollAll<RawDocument>("3m", numberOfSlices)
.Wait(TimeSpan.FromMinutes(5), onNext: s =>
{
var docs = s.SearchResponse.DebugInformation;
var documents = s.SearchResponse.Hits;
foreach (var document in documents)
{
// do something with this set of documents
// business logic to load into the database.
MessageBox.Show("document.Id=" + document.Id);
MessageBox.Show("document.Score=" + document.Score);
MessageBox.Show("document.Source.duration=" + document.Source.duration);
MessageBox.Show("document.Source.var_time=" + document.Source.var_time);
MessageBox.Show("document.Source.var_name=" + document.Source.var_name);
MessageBox.Show("document.Type=" + document.Type);
MessageBox.Show("document.Index=" + document.Index);
}
});
}
An idea:
First, read from root, create map classes from root. Took, TimedOut, Shards and Hits.
Second, your classes are intended for "hits.hits". And this is an array, so it needs an IEnumerable like List. Please finish this by yourself by adding [PropertyName("...")] as needed:
public class OuterHits
{
public string total {get;Set;}
public string max_score {get;Set;}
public List<RawDocument> hits {get;Set;} // or hits[]
}
Also you may need a root class
public class rootClass
{
public string string took {get;Set;}
public string timeout {get;Set;}
public Shards shards {get;Set;}
public OuterHits hits {get;Set;}
}
Also implement Shards class
As point by #Russ Cam, the below solution update is being posted as an answer for others to review.
public class RawDocument
{
[PropertyName("duration")]
public long Duration { get; set; }
[PropertyName("group_id")]
public string GroupId { get; set; }
[PropertyName("var_time")]
public DateTime Vartime { get; set; }
[PropertyName("var_name")]
public string Varname { get; set; }
}
static void Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool)
.DefaultMappingFor<RawDocument>(m => m
.IndexName("test_index")
.TypeName("doc"));
var searchResponse = client.Search<RawDocument>();
var numberOfSlices = 4;
var scrollAllObservable = client.ScrollAll<RawDocument>("3m", numberOfSlices)
.Wait(TimeSpan.FromMinutes(5), onNext: s =>
{
var docs = s.SearchResponse.DebugInformation;
var documents = s.SearchResponse.Hits;
foreach (var document in documents)
{
// do something with this set of documents
// business logic to load into the database.
MessageBox.Show("document.Id=" + document.Id);
MessageBox.Show("document.Score=" + document.Score);
MessageBox.Show("document.Source.duration=" + document.Source.duration);
MessageBox.Show("document.Source.var_time=" + document.Source.var_time);
MessageBox.Show("document.Source.var_name=" + document.Source.var_name);
MessageBox.Show("document.Type=" + document.Type);
MessageBox.Show("document.Index=" + document.Index);
}
});
}
I have pasted JSON in to my C# console application, so it produces classes like so :
public class Rootobject
{
public Id id { get; set; }
}
public class Id
{
public Identifier Identifier { get; set; }
}
public class Identifier
{
public Id1 id { get; set; }
public Type type { get; set; }
}
public class Id1
{
public string IdentifierString { get; set; }
}
public class Type
{
public string IdentifierType { get; set; }
}
I wish to set the values of identifierString and identifierType like so :
var node = new Rootobject();
node.id.Identifier.id.IdentifierString = "testId";
node.id.Identifier.type.IdentifierType = "idType";
However, I get an 'Object reference not set to an instance of an object' error.
you should write:
var node = new Rootobject();
node.id = new Id();
node.id.Identifier = new Identifier();
node.id.Identifier.id = new Id1();
node.id.Identifier.type = new Type();
node.id.Identifier.id.IdentifierString = "testId";
node.id.Identifier.type.IdentifierType = "idType";
or even better:
var node = new Rootobject
{
id = new Id
{
Identifier = new Identifier
{
id = new Id1 { IdentifierString = "id" },
type = new Type { IdentifierType = "type" },
}
}
};
You are missing creation of objects, they are nulls
Or version which compile :)
var node = new RootObject
{
id = new Id
{
Identifier = new Identifier
{
id = new Id1 { IdentifierString = "id" },
type = new Type { IdentifierType = "type" },
}
}
};
I've been working on using reflection but its very new to me still. So the line below works. It returns a list of DataBlockOne
var endResult =(List<DataBlockOne>)allData.GetType()
.GetProperty("One")
.GetValue(allData);
But I don't know myType until run time. So my thoughts were the below code to get the type from the object returned and cast that type as a list of DataBlockOne.
List<DataBlockOne> one = new List<DataBlockOne>();
one.Add(new DataBlockOne { id = 1 });
List<DataBlockTwo> two = new List<DataBlockTwo>();
two.Add(new DataBlockTwo { id = 2 });
AllData allData = new AllData
{
One = one,
Two = two
};
var result = allData.GetType().GetProperty("One").GetValue(allData);
Type thisType = result.GetType().GetGenericArguments().Single();
Note I don't know the list type below. I just used DataBlockOne as an example
var endResult =(List<DataBlockOne>)allData.GetType() // this could be List<DataBlockTwo> as well as List<DataBlockOne>
.GetProperty("One")
.GetValue(allData);
I need to cast so I can search the list later (this will error if you don't cast the returned object)
if (endResult.Count > 0)
{
var search = endResult.Where(whereExpression);
}
I'm confusing the class Type and the type used in list. Can someone point me in the right direction to get a type at run time and set that as my type for a list?
Class definition:
public class AllData
{
public List<DataBlockOne> One { get; set; }
public List<DataBlockTwo> Two { get; set; }
}
public class DataBlockOne
{
public int id { get; set; }
}
public class DataBlockTwo
{
public int id { get; set; }
}
You might need something like this:
var endResult = Convert.ChangeType(allData.GetType().GetProperty("One").GetValue(allData), allData.GetType());
Just guessing, didn't work in C# since 2013, please don't shoot :)
You probably want something like this:
static void Main(string[] args)
{
var one = new List<DataBlockBase>();
one.Add(new DataBlockOne { Id = 1, CustomPropertyDataBlockOne = 314 });
var two = new List<DataBlockBase>();
two.Add(new DataBlockTwo { Id = 2, CustomPropertyDatablockTwo = long.MaxValue });
AllData allData = new AllData
{
One = one,
Two = two
};
#region Access Base Class Properties
var result = (DataBlockBase)allData.GetType().GetProperty("One").GetValue(allData);
var oneId = result.Id;
#endregion
#region Switch Into Custom Class Properties
if (result is DataBlockTwo)
{
var thisId = result.Id;
var thisCustomPropertyTwo = ((DataBlockTwo)result).CustomPropertyDatablockTwo;
}
if (result is DataBlockOne)
{
var thisId = result.Id;
var thisCustomPropertyOne = ((DataBlockOne)result).CustomPropertyDataBlockOne;
}
#endregion
Console.Read();
}
public class AllData
{
public List<DataBlockBase> One { get; set; }
public List<DataBlockBase> Two { get; set; }
}
public class DataBlockOne : DataBlockBase
{
public int CustomPropertyDataBlockOne { get; set; }
}
public class DataBlockTwo : DataBlockBase
{
public long CustomPropertyDatablockTwo { get; set; }
}
public abstract class DataBlockBase
{
public int Id { get; set; }
}
I'm working with KnockoutMVC and it requires strongly type models to use inside the VIEW. I have tried multiple variations of the examples on KnockoutMVC's site including using ENUMS and still could not get it to work. Perhaps this is a problem with the setup of my models.
MODELS
public class PhoneNumber
{
public List<NumberTypeClass> Types { get; set; }
//public NumberType enumType { get; set; }
//public enum NumberType
//{
// Work,
// Home,
// Mobile,
// Fax
//}
private string _number;
[StringLength(14, MinimumLength = 10, ErrorMessage = "Please use (123) 456-7890 format"), Required]
public string Number
{
get
{
this._number = BeautifyPhoneNumber(this._number);
return this._number;
}
set
{
this._number = value;
}
}
public string Extension { get; set; }
public static String BeautifyPhoneNumber(string numberToBeautify)
{
//beautifyNumberCode
}
}
public class NumberTypeClass
{
public int Id { get; set; }
public string NumberType { get; set; }
}
public class VendorsEditorVendorModel
{
public string FirstName {Get;set;}
public string LastName {get;set;}
public List<Address> Address {get;set;}
public List<PhoneNumber> Phones {get;set;}
}
public class VendorsEditorModel
{
public List<VendorsEditorVendorModel> Vendors {get;set;}
}
CONTROLLER
public class VendorsEditorController : BaseController
{
public ActionResult CreateVendors()
{// VendorsEditor/CreateVendors
var vendor = new VendorsEditorModel();
vendor.Vendors = new List<VendorsEditorVendorModel>();
vendor.Vendors[0].Phones[0].Types = new List<NumberTypeClass>
{
new NumberTypeClass{Id = 0, TypeName = "Mobile"},
new NumberTypeClass{Id = 0, TypeName = "Work"},
new NumberTypeClass{Id = 0, TypeName = "Home"}
};//this throws an error because there is no Vendors[0] ...but how would i populate this list for every Vendor?
return View(vendor);
}
}
You cannot call an empty collection by index [x]. You need to fill your collection from a database or what not before you can access items in it. If you are just trying to add items to a collection, this is how you do it:
var vendor = new VendorsEditorModel
{
Vendors = new List<VendorsEditorVendorModel>
{
new VendorsEditorVendorModel
{
Phones = new List<PhoneNumber>
{
new PhoneNumber
{
Types = new List<NumberTypeClass>
{
new NumberTypeClass {Id = 0, NumberType = "Mobile"}
}
}
}
}
}
};
If you just want to add the types to an already populated collection, you can do the following:
foreach (var phone in vendor.Vendors.SelectMany(item => item.Phones))
{
phone.Types = new List<NumberTypeClass>
{
new NumberTypeClass{Id = 0, NumberType = "Mobile"},
new NumberTypeClass{Id = 0, NumberType = "Work"},
new NumberTypeClass{Id = 0, NumberType = "Home"}
};
}