How to force serialization of decimal values inside dictionaries as doubles in mongodb csharp driver? - mongodb-.net-driver

I have the following dictionary:
var dict = new Dictionary<string, object> {
{ "decimal", 3.503m },
{ "int", 45 }
};
var serializedString = dict.ToJson();
By default that is serialized as:
{ "decimal" : { "_t" : "System.Decimal", "_v" : "3.503" }, "int" : 45 }
If I override DecimalSerializer as:
BsonSerializer.RegisterSerializer<decimal>(new DecimalSerializer().WithRepresentation(BsonType.Double));
That only influences on how "_v" value is serialized, e.g.:
{ "decimal" : { "_t" : "System.Decimal", "_v" : 3.503 }, "int" : 45 }
Expected result:
{ "decimal" : 3.503, "int" : 45 }
Please advise

The cause of the .Net types in the bson, is the lack of type in the dictionary. The Bson serializers are trying to get enough state to restore the original object of the items in the dictionary. From the context (the dictionary) they are of type "object", so the .Net type is inserted to know enough when deserializing.
The following solutions answer your question but lose the type information for deserializing.
Solution 1: Change the dictionary type to <string, decimal>
var dict = new Dictionary<string, decimal> {
{ "decimal", 3.503m },
{ "int", 45 }
};
var serializedString = dict.ToJson();
Results in: { "decimal" : "3.503", "int" : "45" }
With your override of the decimal serializer, you get the expected result.
{ "decimal" : 3.503, "int" : 45 }
Solution 2: Change the dictionary type to <string, double>
var dict = new Dictionary<string, double> {
{ "decimal", (double)3.503m },
{ "int", 45 }
};
var serializedString = dict.ToJson();
Results in the expected result: { "decimal" : 3.503, "int" : 45 }
Solution 3: Use custom serializer
public class MyDictionarySerializer : SerializerBase<Dictionary<string, object>>
{
public override void Serialize(MongoDB.Bson.Serialization.BsonSerializationContext context, MongoDB.Bson.Serialization.BsonSerializationArgs args, Dictionary<string, object> dictionary)
{
context.Writer.WriteStartArray();
foreach (var item in dictionary)
{
context.Writer.WriteStartDocument();
context.Writer.WriteString(item.Key);
// TODO your converstions from object to double
var value = (double)item.Value;
context.Writer.WriteDouble(value);
context.Writer.WriteEndDocument();
}
context.Writer.WriteEndArray();
}
public override Dictionary<string, object> Deserialize(MongoDB.Bson.Serialization.BsonDeserializationContext context, MongoDB.Bson.Serialization.BsonDeserializationArgs args)
{
context.Reader.ReadStartArray();
var result = new Dictionary<string, object>();
while (true)
{
try
{
//this catch block only need to identify the end of the Array
context.Reader.ReadStartDocument();
}
catch (Exception exp)
{
context.Reader.ReadEndArray();
break;
}
var key = context.Reader.ReadString();
double value = context.Reader.ReadDouble();
result.Add(key, value);
context.Reader.ReadEndDocument();
}
return result;
}
}

As another option, it's possible to override object serializer
public class DecimalsOverridingObjectSerializer : ObjectSerializer
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) {
if (value != null && value is decimal) {
base.Serialize(context, args, Convert.ToDouble(value));
} else {
base.Serialize(context, args, value);
}
}
}
BsonSerializer.RegisterSerializer(typeof(object), new DecimalsOverridingObjectSerializer());
that still will not work for Hashtables.
Possible workaround for Hashtables:
public class DecimalsOverridingDictionarySerializer<TDictionary>:
DictionaryInterfaceImplementerSerializer<TDictionary>
where TDictionary : class, IDictionary, new()
{
public DecimalsOverridingDictionarySerializer(DictionaryRepresentation dictionaryRepresentation)
: base(dictionaryRepresentation, new DecimalsOverridingObjectSerializer(), new DecimalsOverridingObjectSerializer())
{ }
}
BsonSerializer.RegisterSerializer(typeof(Hashtable), new DecimalsOverridingDictionarySerializer<Hashtable>(DictionaryRepresentation.Document));

Related

ArgumentException when deserializing using JSON.Net

I have an object as follows,
public class Foo
{
public Dictionary<string, List<double?>> Bar { get; set; }
}
I serialize using, string myJson = JsonConvert.Serialize(myFoo) and get sensible Json. However when I run JsonConvert.Deserialize<Foo>(myJson) I get an ArgumentException Parameter name: value.
Why is this?
I am using Json.Net on a Windows Phone 7.1 project.
Edit: Here is an example object and the Json it produces,
Foo myFoo = new Foo()
{
Bar = new Dictionary<string,List<double?>>() {
{"Flim", new List<double?>() { 0, 0.2, null, 0.9 }},
{"Flam", new List<double?>() { 0.0,0.1, null, null}},
}
};
The contents of myJson after serialization (double quote escapes removed)
{"Bar":{"Flim":[0.0,0.2,null,0.9],"Flam":[0.0,0.1,null,null]}}
It worked fine for me using Json.Net 4.5.11 on .NET 4.5 in standard Windows. Here is the program I used (below). Can you try running this exact code in your environment and see if it works? If so, then your ArgumentException must be coming from somewhere else. If not, then that seems to point to a difference between Windows and Windows Phone environments.
class Program
{
static void Main(string[] args)
{
Foo myFoo = new Foo()
{
Bar = new Dictionary<string, List<double?>>()
{
{ "Flim", new List<double?>() { 0, 0.2, null, 0.9 } },
{ "Flam", new List<double?>() { 0.0, 0.1, null, null } },
}
};
string json = JsonConvert.SerializeObject(myFoo);
Console.WriteLine(json);
Foo foo2 = JsonConvert.DeserializeObject<Foo>(json);
foreach (KeyValuePair<string, List<double?>> kvp in foo2.Bar)
{
Console.Write(kvp.Key);
Console.Write(":");
string sep = " {";
foreach (double? d in kvp.Value)
{
Console.Write(sep);
Console.Write(d.HasValue ? d.Value.ToString() : "null");
sep = ", ";
}
Console.WriteLine("}");
}
}
public class Foo
{
public Dictionary<string, List<double?>> Bar { get; set; }
}
}
Output:
{"Bar":{"Flim":[0.0,0.2,null,0.9],"Flam":[0.0,0.1,null,null]}}
Flim: {0, 0.2, null, 0.9}
Flam: {0, 0.1, null, null}

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

EmitMapper and List

It's the first time that I use EmitMapper.
I have a list of object ex: Customer and I would like to map this list in a ienumerable of CustomerDTO how can I do that?
Tnx
It's straightforward if you have a list and want to convert it to list of DTOs:
var mapper = ObjectMapperManager.DefaultInstance.GetMapper<Customer, CustomerDTO>();
IEnumerable<CustomerDTO> dtos = listOfCustomer.Select(mapper.map);
The preblem is when the list is in another object, for example User and UserDTO:
class User {
public List<Customer> Customers { get; set; }
}
class UserDTO {
public IEnumerable<CustomerDTO> Customers { get; set; }
}
It seems that EmitMapper does not support conversion from List to Enumerable. A way to support it would be:
var customerMapper = ObjectMapperManager
.DefaultInstance.GetMapper<Customer, CustomerDTO>();
var mapper = ObjectMapperManager.DefaultInstance
.GetMapper<User, UserDTO>(
new DefaultMapConfig()
.ConvertUsing<List<Customer>, IEnumerable<CustomerDTO>>(
a => a.Select(customerMapper.Map))
);
This can be done creating a custom class, implementing the interface "ICustomConverterProvider" and adding a ConvertGeneric to the "DefaultMapConfig".
Looking on the source code of EmitMapper, i found a class named "ArraysConverterProvider", which is the default generic converter from ICollections to Arrays.
Adapting the code from this class to work with IEnumerable collections:
class GenericIEnumerableConverterProvider : ICustomConverterProvider
{
public CustomConverterDescriptor GetCustomConverterDescr(
Type from,
Type to,
MapConfigBaseImpl mappingConfig)
{
var tFromTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(from);
var tToTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(to);
if (tFromTypeArgs == null || tToTypeArgs == null || tFromTypeArgs.Length != 1 || tToTypeArgs.Length != 1)
{
return null;
}
var tFrom = tFromTypeArgs[0];
var tTo = tToTypeArgs[0];
if (tFrom == tTo && (tFrom.IsValueType || mappingConfig.GetRootMappingOperation(tFrom, tTo).ShallowCopy))
{
return new CustomConverterDescriptor
{
ConversionMethodName = "Convert",
ConverterImplementation = typeof(GenericIEnumerableConverter_OneTypes<>),
ConverterClassTypeArguments = new[] { tFrom }
};
}
return new CustomConverterDescriptor
{
ConversionMethodName = "Convert",
ConverterImplementation = typeof(GenericIEnumerableConverter_DifferentTypes<,>),
ConverterClassTypeArguments = new[] { tFrom, tTo }
};
}
}
class GenericIEnumerableConverter_DifferentTypes<TFrom, TTo> : ICustomConverter
{
private Func<TFrom, TTo> _converter;
public IEnumerable<TTo> Convert(IEnumerable<TFrom> from, object state)
{
if (from == null)
{
return null;
}
TTo[] result = new TTo[from.Count()];
int idx = 0;
foreach (var f in from)
{
result[idx++] = _converter(f);
}
return result;
}
public void Initialize(Type from, Type to, MapConfigBaseImpl mappingConfig)
{
var staticConverters = mappingConfig.GetStaticConvertersManager() ?? StaticConvertersManager.DefaultInstance;
var staticConverterMethod = staticConverters.GetStaticConverter(typeof(TFrom), typeof(TTo));
if (staticConverterMethod != null)
{
_converter = (Func<TFrom, TTo>)Delegate.CreateDelegate(
typeof(Func<TFrom, TTo>),
null,
staticConverterMethod
);
}
else
{
_subMapper = ObjectMapperManager.DefaultInstance.GetMapperImpl(typeof(TFrom), typeof(TTo), mappingConfig);
_converter = ConverterBySubmapper;
}
}
ObjectsMapperBaseImpl _subMapper;
private TTo ConverterBySubmapper(TFrom from)
{
return (TTo)_subMapper.Map(from);
}
}
class GenericIEnumerableConverter_OneTypes<T>
{
public IEnumerable<T> Convert(IEnumerable<T> from, object state)
{
if (from == null)
{
return null;
}
return from;
}
}
This code is just a copy with a minimum of adaptation as possible and can be applyed to objects with many levels of hierarchy.
You can use the above code with the following command:
new DefaultMapConfig().ConvertGeneric(
typeof(IEnumerable<>),
typeof(IEnumerable<>),
new GenericIEnumerableConverterProvider());
This saved my day and I hope to save yours too! hehehe

Hierarchical structure iteration and LINQ

Assume that we have class
public class RMenuItem
{
public List<RMenuItem> ChildrenItems { get; }
public decimal OperationID { get; }
public string Name { get; }
}
as you can see - each menuitem could have children items - as usual in menu.
My task is to iterate through each items of this list and apply some action to it. Classical decision is to write recursive iteration. But I'm interesting if LINQ could make my task easier? For example, I suppose that we can write query that can get flat list of objects, which i can iterate simply with foreach. But my attempts in this way weren't successful yet.
So any help appreciated!
It's possible:
public void PrintAllNames(RMenuItem rootItem)
{
Action<RMenuItem> print = null;
print = m =>
{
Console.WriteLine(m.Name);
m.ChildrenItems.ForEach(print);
};
print(rootItem);
}
Notice how it's necessary to declare print so that print can use itself. This is directly comparable to a recursive method, which I'd rather use:
public void PrintAllNames(RMenuItem rootItem)
{
Console.WriteLine(rootItem.Name);
rootItem.ChildrenItems.ForEach(PrintAllNames);
}
(although for a more complex situation, maybe the functional solution would make the most sense)
I suggest 2 ways of achieving this. You can opt with an utility method to get all the items or you can implement the Visitor Pattern, though it implies changing the RMenuItem class.
Utility method:
static IEnumerable<RMenuItem> GetAllMenuItems(IList<RMenuItem> items)
{
if (items == null)
throw new ArgumentNullException("items");
Queue<RMenuItem> queue = new Queue<RMenuItem>(items);
while (queue.Count > 0)
{
var item = queue.Dequeue();
if (item.ChildrenItems != null)
{
foreach (var child in item.ChildrenItems)
{
queue.Enqueue(child);
}
}
yield return item;
}
}
I prefer an imperative way to a recursive because we can use iterator blocks.
Visitor Pattern:
public interface IRMenuItemVisitor
{
void Visit(RMenuItem item);
}
public class PrintRMenuItemVisitor : IRMenuItemVisitor
{
public void Visit(RMenuItem item)
{
Console.WriteLine(item);
}
}
public interface IRMenuItem
{
void Accept(IRMenuItemVisitor visitor);
}
public class RMenuItem : IRMenuItem
{
// ...
public void Accept(IRMenuItemVisitor visitor)
{
visitor.Visit(this);
if (ChildrenItems != null)
{
foreach (var item in ChildrenItems)
{
item.Accept(visitor);
}
}
}
}
Usage:
RMenuItem m1 = new RMenuItem
{
Name = "M1",
ChildrenItems = new List<RMenuItem> {
new RMenuItem { Name = "M11" },
new RMenuItem {
Name = "M12",
ChildrenItems = new List<RMenuItem> {
new RMenuItem { Name = "M121" },
new RMenuItem { Name = "M122" }
}
}
}
};
RMenuItem m2 = new RMenuItem
{
Name = "M2",
ChildrenItems = new List<RMenuItem> {
new RMenuItem { Name = "M21" },
new RMenuItem { Name = "M22" },
new RMenuItem { Name = "M23" }
}
};
IList<RMenuItem> menus = new List<RMenuItem> { m1, m2 };
foreach (var menu in GetAllMenuItems(menus))
{
Console.WriteLine(menu);
}
// or
IList<RMenuItem> menus = new List<RMenuItem> { m1, m2 };
foreach (var menu in menus)
{
menu.Accept(new PrintRMenuItemVisitor());
}
You could difine a Flatten method in your class (or as an extension if you prefer) like this
public IEnumerable<RMenuItem> Flatten()
{
foreach (var item in ChildrenItems)
{
yield return item;
}
return ChildrenItems.SelectMany(item => item.Flatten());
}
then doing somthing with each elements will be as simple as
RMenuItem rootItem ;
// do somthing with the root item
foreach (var item in rootItem.Flatten())
{
// do somthing
}
Indeed you can do that using LINQ, SelectMany flats out the list, just some example
menuItemsList.SelectMany(x => x.ChildrenItems).Where(c => c.someChildProperty);
Thanks
Edit:
In response to the comments, I was just giving an example of SelectMany previously. Thanks for pointing out.
menuItemsList.SelectMany(x => x.ChildrenItems.Select(p => p)).Where(c => c.someChildProperty);
OR something like this
menuItemsList.SelectMany(x => x.ChildrenItems).Select(p => p).Where(c => c.someChildProperty);
Edit2
Ahh .. now I understood what you want ..
We can just slightly modify my above query to do what you want
menuItemsList
.SelectMany(x => { //do something with x like printing it
x.ChildrenItems
})
.Select(p => { // do something with p like printing it
p
});
Basically you can do what you want the element inside the {}
Thanks

LinQ distinct with custom comparer leaves duplicates

I've got the following classes:
public class SupplierCategory : IEquatable<SupplierCategory>
{
public string Name { get; set; }
public string Parent { get; set; }
#region IEquatable<SupplierCategory> Members
public bool Equals(SupplierCategory other)
{
return this.Name == other.Name && this.Parent == other.Parent;
}
#endregion
}
public class CategoryPathComparer : IEqualityComparer<List<SupplierCategory>>
{
#region IEqualityComparer<List<SupplierCategory>> Members
public bool Equals(List<SupplierCategory> x, List<SupplierCategory> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<SupplierCategory> obj)
{
return obj.GetHashCode();
}
#endregion
}
And i'm using the following linq query:
CategoryPathComparer comparer = new CategoryPathComparer();
List<List<SupplierCategory>> categoryPaths = (from i in infoList
select
new List<SupplierCategory>() {
new SupplierCategory() { Name = i[3] },
new SupplierCategory() { Name = i[4], Parent = i[3] },
new SupplierCategory() { Name = i[5], Parent = i[4] }}).Distinct(comparer).ToList();
But the distinct does not do what I want it to do, as the following code demonstrates:
comp.Equals(categoryPaths[0], categoryPaths[1]); //returns True
Am I using this in a wrong way? why are they not compared as I intend them to?
Edit:
To demonstrate the the comparer does work, the following returns true as it should:
List<SupplierCategory> list1 = new List<SupplierCategory>() {
new SupplierCategory() { Name = "Cat1" },
new SupplierCategory() { Name = "Cat2", Parent = "Cat1" },
new SupplierCategory() { Name = "Cat3", Parent = "Cat2" }
};
List<SupplierCategory> list1 = new List<SupplierCategory>() {
new SupplierCategory() { Name = "Cat1" },
new SupplierCategory() { Name = "Cat2", Parent = "Cat1" },
new SupplierCategory() { Name = "Cat3", Parent = "Cat2" }
};
CategoryPathComparer comp = new CategoryPathComparer();
Console.WriteLine(comp.Equals(list1, list2).ToString());
Your problem is that you didn't implement IEqualityComparer correctly.
When you implement IEqualityComparer<T>, you must implement GetHashCode so that any two equal objects have the same hashcode.
Otherwise, you will get incorrect behavior, as you're seeing here.
You should implement GetHashCode as follows: (courtesy of this answer)
public int GetHashCode(List<SupplierCategory> obj) {
int hash = 17;
foreach(var value in obj)
hash = hash * 23 + obj.GetHashCode();
return hash;
}
You also need to override GetHashCode in SupplierCategory to be consistent. For example:
public override int GetHashCode() {
int hash = 17;
hash = hash * 23 + Name.GetHashCode();
hash = hash * 23 + Parent.GetHashCode();
return hash;
}
Finally, although you don't need to, you should probably override Equals in SupplierCategory and make it call the Equals method you implemented for IEquatable.
Actually, this issue is even covered in documentation:
http://msdn.microsoft.com/en-us/library/bb338049.aspx.

Resources