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}
Related
How to make a ConcurrentQueue to be cleaned by the condition for the first elements. Eg to clear older blog posts. I came up with this idea of a ConditionConcurrentQueue:
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
public class ConditionConcurrentQueue<T> : ConcurrentQueue<T>
where T: class
{
public ConditionConcurrentQueue(Func<T, bool> condition)
: this(null, condition)
{ }
public ConditionConcurrentQueue(IEnumerable<T> items, Func<T, bool> condition)
: base(items)
{
_condition = condition;
}
private Func<T, bool> _condition;
public virtual void Enqueue(T item)
{
T removed;
bool cleaningRun = true;
int failedCnt = 0;
while (!IsEmpty && cleaningRun && failedCnt < 10)
{
if (TryPeek(out removed))
{
bool result = _condition.Invoke(removed);
if (!result)
{
if (!TryDequeue(out removed))
{
failedCnt++;
Thread.Sleep(10);
}
}
else
cleaningRun = false;
}
else
{
failedCnt++;
Thread.Sleep(10);
}
}
base.Enqueue(item);
}
}
Use this ConditionConcurrentQueue could be so:
class Blog
{
public ConditionConcurrentQueue<Post> Posts { get; set; }
}
class Post
{
public DateTime Time { get; set; }
public string Text { get; set; }
}
class Program
{
static void Main(string[] args)
{
Blog blog = new Blog
{
Posts = new ConditionConcurrentQueue<Post>(
new Post[] {
new Post { Time = DateTime.Now - TimeSpan.FromMinutes(80), Text = "Title 1" },
new Post { Time = DateTime.Now - TimeSpan.FromMinutes(60), Text = "Title 2" },
new Post { Time = DateTime.Now - TimeSpan.FromMinutes(40), Text = "Title 3" },
},
p => p.Time > DateTime.Now - TimeSpan.FromHours(1))
};
blog.Posts.Enqueue(new Post { Time = DateTime.Now - TimeSpan.FromMinutes(20), Text = "Title 4" });
foreach (Post post in blog.Posts.ToList())
Console.WriteLine(post.Text);
}
}
maybe it is too primitive solution.
I would appreciate any improvements. Thanks.
also, you can try through the extension method:
public static ICollection<T> Enqueue<T>(this ConcurrentQueue<T> field, T item, Func<T, bool> predicate)
{
ICollection<T> removed = field.TryDequeue<T>(predicate);
field.Enqueue(item);
return removed;
}
public static ICollection<T> TryDequeue<T>(this ConcurrentQueue<T> field, Func<T, bool> predicate)
{
T comparedItem;
var removedList = new List<T>();
while (field.TryPeek(out comparedItem))
{
if (!predicate.Invoke(comparedItem))
{
if (field.TryDequeue(out comparedItem))
removedList.Add(comparedItem);
else
break;
}
else
break;
}
return removedList;
}
As of .NET Core 2.0 / .NET Standard 2.1 / .NET Framework 5.0, there is a Clear() method on ConcurrentQueue<T>. See: ConcurrentQueue.Clear.
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));
Developing a new MVC4 app, I have followed this example on the JSON.net website to fill my viewmodel with a new JSON JObject:
FinalViewModel finalVM= new FinalViewModel();
IList<ResultModel> results = GetResultList();
FinalVM.QueryResults = results;
JObject myJSON = new JObject(
new JProperty("grid",
new JArray(
from g in results
group g by g.ResultYear into y
orderby y.Key
select new JObject {
new JProperty("Year", y.Key),
new JProperty("min_1", y.Min(g=> g.Result_val1)),
new JProperty("min_2", y.Min(g=> g.Result_val2)),
new JProperty("items",
new JArray(
from g in results
where g.ResultYear==y.Key
orderby g.id
select new JObject(
new JProperty("l", g.Result_val1),
new JProperty("j",g.Result_val2),
new JProperty("id", g.id)
)
)
)}
)));
FinalVM.DataJson = myJSON;
return PartialView("_JSONView", FinalVM);
Everything works fine and i get this type of json sent to my view:
{
"grid": [
{
"Year": 1998,
"min_val1": "12",
"min_val2": null,
"items": [
{
"l": 12,
"j": null,
"id": 60
},
{
"l": 25,
"j": null,
"id": 61
}
]
}
]
}
I would like to get rid of the null values when they exist. I read a lot about the NullValueHandling option but do not see how to use it into my Json.Net linq code.
Instead of creating JObjects as part of your LINQ transformation, use anonymous objects. (This will also make your code a heck of a lot more readable!) Afterward, you can load the result object into a JObject using a JsonSerializer instance that has NullValueHandling set to Ignore. This will get rid of the nulls. Here's a demo:
class Program
{
static void Main(string[] args)
{
IList<ResultModel> results = new List<ResultModel>
{
new ResultModel
{
id = 60,
ResultYear = 1998,
Result_val1 = 12,
Result_val2 = null
},
new ResultModel
{
id = 61,
ResultYear = 1998,
Result_val1 = 25,
Result_val2 = null
}
};
var groupedResult = new
{
grid = from g in results
group g by g.ResultYear into y
orderby y.Key
select new
{
Year = y.Key,
min_1 = y.Min(g => g.Result_val1),
min_2 = y.Min(g => g.Result_val2),
items = from g in results
where g.ResultYear == y.Key
orderby g.id
select new
{
l = g.Result_val1,
j = g.Result_val2,
id = g.id
}
}
};
JsonSerializer serializer = new JsonSerializer();
serializer.NullValueHandling = NullValueHandling.Ignore;
JObject myJSON = JObject.FromObject(groupedResult, serializer);
Console.WriteLine(myJSON.ToString(Formatting.Indented));
}
class ResultModel
{
public int id { get; set; }
public int ResultYear { get; set; }
public int? Result_val1 { get; set; }
public int? Result_val2 { get; set; }
}
}
Output:
{
"grid": [
{
"Year": 1998,
"min_1": 12,
"items": [
{
"l": 12,
"id": 60
},
{
"l": 25,
"id": 61
}
]
}
]
}
One other note: if you are not planning to manipulate the JSON, you can actually skip the JObject altogether and serialize your grouped result directly to string instead:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.NullValueHandling = NullValueHandling.Ignore;
settings.Formatting = Formatting.Indented;
string myJSON = JsonConvert.SerializeObject(groupedResult, settings);
I'm having hard time understanding how to convert an Enum value to it's corresponding name. My model is as follows:
public class CatalogRule
{
public int ID { get; set; }
[Display(Name = "Catalog"), Required]
public int CatalogID { get; set; }
[Display(Name = "Item Rule"), Required]
public ItemType ItemRule { get; set; }
public string Items { get; set; }
[Display(Name = "Price Rule"), Required]
public PriceType PriceRule { get; set; }
[Display(Name = "Value"), Column(TypeName = "MONEY")]
public decimal PriceValue { get; set; }
[Display(Name = "Exclusive?")]
public bool Exclude { get; set; }
}
public enum ItemType
{
Catalog,
Category,
Group,
Item
}
public enum PriceType
{
Catalog,
Price_A,
Price_B,
Price_C
}
A sample result from .net API:
[
{
$id: "1",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 1,
CatalogID: 501981,
ItemRule: 0,
Items: "198",
PriceRule: 1,
PriceValue: 0.5,
Exclude: false
},
{
$id: "2",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 2,
CatalogID: 501981,
ItemRule: 2,
Items: "9899",
PriceRule: 2,
PriceValue: 10.45,
Exclude: false
}
]
So in this example, I need to get Catalog for results[0].ItemRule & Price A for results[0].PriceRule. How can I accomplish this in BreezeJS??
This is easy to do in ASP.NET Web API, because it is an out-of-box feature in the default JSON serializer (Json.NET).
To see strings instead of enum numbers in JSON, just add an instance of StringEnumConverter to JSON serializer settings during app init:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
UPDATE: Yep, you right, this is not help with Breeze.js. Ok, you can anyway do a little magic to make enums work like strings (while new version with fix is not released).
Create a custom ContextProvider which updates all integer enum values in metadata to strings. Here it is:
public class StringEnumEFContextProvider<T> : EFContextProvider<T>
where T : class, new()
{
protected override string BuildJsonMetadata()
{
XDocument xDoc;
if (Context is DbContext)
{
xDoc = GetCsdlFromDbContext(Context);
}
else
{
xDoc = GetCsdlFromObjectContext(Context);
}
var schemaNs = "http://schemas.microsoft.com/ado/2009/11/edm";
foreach (var enumType in xDoc.Descendants(XName.Get("EnumType", schemaNs)))
{
foreach (var member in enumType.Elements(XName.Get("Member", schemaNs)))
{
member.Attribute("Value").Value = member.Attribute("Name").Value;
}
}
return CsdlToJson(xDoc);
}
}
And use it instead of EFContextProvider in your Web API controllers:
private EFContextProvider<BreezeSampleContext> _contextProvider =
new StringEnumEFContextProvider<BreezeSampleContext>();
This works well for me with current Breeze.js version (1.1.3), although I haven't checked other scenarios, like validation...
UPDATE: To fix validation, change data type for enums in breeze.[min|debug].js, manually (DataType.fromEdmDataType function, dt = DataType.String; for enum) or replace default function during app init:
breeze.DataType.fromEdmDataType = function (typeName) {
var dt = null;
var parts = typeName.split(".");
if (parts.length > 1) {
var simpleName = parts[1];
if (simpleName === "image") {
// hack
dt = DataType.Byte;
} else if (parts.length == 2) {
dt = DataType.fromName(simpleName);
if (!dt) {
if (simpleName === "DateTimeOffset") {
dt = DataType.DateTime;
} else {
dt = DataType.Undefined;
}
}
} else {
// enum
dt = DataType.String; // THIS IS A FIX!
}
}
return dt;
};
Dirty, dirty hacks, I know... But that's the solution I found
There will be a new release out in the next few days where we "change" breeze's enum behavior ( i.e. break existing code with regards to enums). In the new release enums are serialized and queried by their .NET names instead of as integers. I will post back here when the new release is out.
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.