I'm trying to read a JSON object which contains the date/time in a format that cannot be directly parsed by .NET's DateTime structure. In order to avoid having an 'int' field in my structure for the date/time, I wrote a custom DateTimeConverter:
public class DateTimeConverter : JavaScriptConverter {
public override IEnumerable<Type> SupportedTypes {
get { return new Type[] { typeof(DateTime), typeof(DateTime?) }; }
}
public override IDictionary<string, object> Serialize(
object obj, JavaScriptSerializer serializer
) { throw new NotImplementedException(); }
public override object Deserialize(
IDictionary<string, object> dictionary, Type type,
JavaScriptSerializer serializer
) {
return DateTime.Now;
}
}
But when I read a JSON string with the JavaScriptSerializer, it does not use my custom converter:
public struct TextAndDate {
public string Text;
public DateTime Date;
}
static void Main() {
string json =
"{" +
" \"text\": \"hello\", " +
" \"date\": \"1276692024\"" +
"}";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new [] { new DateTimeConverter() });
var test = serializer.Deserialize<TextAndDate>(json);
}
The converter is used when I directly deserialize a DateTime value, just not when I deserialize a type containing a DateTime value.
Why?
Any way around this without writing a custom DateTime type or using int?
You should make small changes in your DateTimeConverter class:
public class DateTimeConverter : JavaScriptConverter {
public override IEnumerable<Type> SupportedTypes {
get { return new Type[] { typeof (TextAndDate) }; }
}
public override IDictionary<string, object> Serialize (
object obj, JavaScriptSerializer serializer
) { throw new NotImplementedException (); }
public override object Deserialize (
IDictionary<string, object> dictionary, Type type,
JavaScriptSerializer serializer
) {
if (type == typeof (TextAndDate)) {
TextAndDate td = new TextAndDate ();
if (dictionary.ContainsKey ("text"))
td.Text = serializer.ConvertToType<string> (
dictionary["text"]);
//if (dictionary.ContainsKey ("date"))
td.Date = DateTime.Now;
return td;
}
else
return null;
}
}
UPDATED based on comment: It seems to me that you should use Message Inspectors technique (see http://msdn.microsoft.com/en-us/library/aa717047.aspx). Look at How to ignore timezone of DateTime in .NET WCF client? for an example.
Related
I'm changing a webform website to use StateServer and now I'm trying to find a way to serialize and deserialize AjaxFileUploadEventArgs, my code so far:
In the html I have:
<ajaxToolkit:AjaxFileUpload
ID="AAA"
runat="server"
OnUploadComplete="OnUploadComplete"
ViewStateMode="Enabled" />
Server:
protected void OnUploadComplete(object sender, AjaxFileUploadEventArgs file)
{
UpdateListInSession(file);
}
public static void UpdateListInSession(AjaxFileUploadEventArgs file)
{
var serializer = new JavaScriptSerializer();
var fileSerialized = serializer.Serialize(file);
}
public static AjaxFileUploadEventArgs GetLeadsListFromSession()
{
var serializer = new JavaScriptSerializer();
AjaxFileUploadEventArgs file = null;
AjaxFileUploadEventArgs deserializeFile =
serializer.Deserialize<AjaxFileUploadEventArgs>(
HttpContext.Current.Session[k_file] as string);
return deserializeFile;
}
The error:
System.MissingMethodException: 'No parameterless constructor defined for type of 'AjaxControlToolkit.AjaxFileUploadEventArgs'.'
Assuming you are using AjaxFileUploadEventArgs.cs from ajaxcontroltoolkit, the exception message is self-explanatory. The serializer you are using, JavaScriptSerializer, can only construct and deserialize a type with a parameterless constructor, but as shown in its reference source, AjaxFileUploadEventArgs only has a single constructor, which is parameterized:
public AjaxFileUploadEventArgs(string fileId, AjaxFileUploadState state, string statusMessage, string fileName, int fileSize, string contentType) {
// Initialize fields
}
So, what are your options to deserialize this type? Firstly, you could switch to json.net which supports parameterized constructors out of the box. Once Json.NET is installed, if you do:
var deserializeFile =
Newtonsoft.Json.JsonConvert.DeserializeObject<AjaxFileUploadEventArgs>(jsonString);
Then it simply works. Sample fiddle. Note that Microsoft's own documentation for JavaScriptSerializer states:
Json.NET should be used serialization and deserialization.
So this is likely the best solution.
If you cannot use Json.NET for whatever reason, you will need to write a custom JavaScriptConverter for AjaxFileUploadEventArgs such as the following:
public class AjaxFileUploadEventArgsConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var args = new AjaxFileUploadEventArgs
(
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "FileId"),
serializer.ConvertItemToTypeOrDefault<AjaxFileUploadState>(dictionary, "State"),
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "StatusMessage"),
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "FileName"),
serializer.ConvertItemToTypeOrDefault<int>(dictionary, "FileSize"),
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "ContentType")
)
{ PostedUrl = serializer.ConvertItemToTypeOrDefault<string>(dictionary, "PostedUrl") };
return args;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(AjaxFileUploadEventArgs) }; }
}
}
public static class JavaScriptSerializerExtensions
{
public static T ConvertItemToTypeOrDefault<T>(this JavaScriptSerializer serializer, IDictionary<string, object> dictionary, string key)
{
object value;
if (!dictionary.TryGetValue(key, out value))
return default(T);
return serializer.ConvertToType<T>(value);
}
}
Then deserialize as follows:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new AjaxFileUploadEventArgsConverter() });
var deserializeFile = serializer.Deserialize<AjaxFileUploadEventArgs>(jsonString);
I have an issue while deserializing json data which can have both float or array type of data. The same issue from here
Dealing with JSON field that holds different types in C#
But everywhere the solution is to use json.net with a JsonConverter. I need to achieve the deserialization using only System.Web.Script.Serialization.JavaScriptSerializer in c#. Can anyone help, pls?
You can use a JavaScriptConverter for this purpose. However, unlike Json.NET's JsonConverter a JavaScriptConverter can only be used for types that map from and to a JSON object -- not an array or primitive type. Thus you will need to create a custom converter for any object that may contain a polymorphic property that could be an array or singleton item.
Let's imagine you have JSON that looks like the following:
{
"name": "my name",
"data": {
"foo": "Foo",
"bar": "Bar"
},
"values": [
3.14,
2.718
]
}
Where "values" might sometimes be a primitive value like so:
"values": 3.14
And, you want to map this to the following POCO:
public class RootObject
{
public string name { get; set; }
public NestedData data { get; set; }
public float[] Values { get; set; }
}
public class NestedData
{
public string foo { get; set; }
public string bar { get; set; }
}
As JavaScriptConverter.Deserialize() is passed an IDictionary<string, object> of parsed values, the steps to take are:
Detach any properties that need custom processing (keeping in mind that JavaScriptSerializer is case-insensitive but that the dictionary is not).
Generate a default deserialization for any remaining properties using JavaScriptSerializer.ConvertToType<T>() using a fresh serializer that does not contain the converter.
Manually deserialize and populate the custom properties into the partially deserialized object, and return it.
For the type shown above, the following converter, based somewhat on this answer, does the job:
class RootObjectConverter : CustomPropertiesConverter<RootObject>
{
const string ValuesName = "values";
protected override IEnumerable<string> CustomProperties
{
get { return new[] { ValuesName }; }
}
protected override void DeserializeCustomProperties(Dictionary<string, object> customDictionary, RootObject obj, JavaScriptSerializer serializer)
{
object itemCost;
if (customDictionary.TryGetValue(ValuesName, out itemCost) && itemCost != null)
obj.Values = serializer.FromSingleOrArray<float>(itemCost).ToArray();
}
protected override void SerializeCustomProperties(RootObject obj, Dictionary<string, object> dict, JavaScriptSerializer serializer)
{
obj.Values.ToSingleOrArray(dict, ValuesName);
}
}
public abstract class CustomPropertiesConverter<T> : JavaScriptConverter
{
protected abstract IEnumerable<string> CustomProperties { get; }
protected abstract void DeserializeCustomProperties(Dictionary<string, object> customDictionary, T obj, JavaScriptSerializer serializer);
protected abstract void SerializeCustomProperties(T obj, Dictionary<string, object> dict, JavaScriptSerializer serializer);
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
// Detach custom properties
var customDictionary = new Dictionary<string, object>();
foreach (var key in CustomProperties)
{
object value;
if (dictionary.TryRemoveInvariant(key, out value))
customDictionary.Add(key, value);
}
// Deserialize and populate all members other than "values"
var obj = new JavaScriptSerializer().ConvertToType<T>(dictionary);
// Populate custom properties
DeserializeCustomProperties(customDictionary, obj, serializer);
return obj;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
// Generate a default serialization. Is there an easier way to do this?
var defaultSerializer = new JavaScriptSerializer();
var dict = defaultSerializer.Deserialize<Dictionary<string, object>>(defaultSerializer.Serialize(obj));
// Remove default serializations of custom properties, if present
foreach (var key in CustomProperties)
{
dict.RemoveInvariant(key);
}
// Add custom properties
SerializeCustomProperties((T)obj, dict, serializer);
return dict;
}
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(T) }; }
}
}
public static class JavaScriptSerializerObjectExtensions
{
public static void ReplaceInvariant<T>(this IDictionary<string, T> dictionary, string key, T value)
{
RemoveInvariant(dictionary, key);
dictionary.Add(key, value);
}
public static bool TryRemoveInvariant<T>(this IDictionary<string, T> dictionary, string key, out T value)
{
if (dictionary == null)
throw new ArgumentNullException();
var keys = dictionary.Keys.Where(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase)).ToArray();
if (keys.Length == 0)
{
value = default(T);
return false;
}
else if (keys.Length == 1)
{
value = dictionary[keys[0]];
dictionary.Remove(keys[0]);
return true;
}
else
{
throw new ArgumentException(string.Format("Duplicate keys found: {0}", String.Join(",", keys)));
}
}
public static void RemoveInvariant<T>(this IDictionary<string, T> dictionary, string key)
{
if (dictionary == null)
throw new ArgumentNullException();
foreach (var actualKey in dictionary.Keys.Where(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase)).ToArray())
dictionary.Remove(actualKey);
}
public static void ToSingleOrArray<T>(this ICollection<T> list, IDictionary<string, object> dictionary, string key)
{
if (dictionary == null)
throw new ArgumentNullException();
if (list == null || list.Count == 0)
dictionary.RemoveInvariant(key);
else if (list.Count == 1)
dictionary.ReplaceInvariant(key, list.First());
else
dictionary.ReplaceInvariant(key, list.ToArray());
}
public static List<T> FromSingleOrArray<T>(this JavaScriptSerializer serializer, object value)
{
if (value == null)
return null;
if (value.IsJsonArray())
{
return value.AsJsonArray().Select(i => serializer.ConvertToType<T>(i)).ToList();
}
else
{
return new List<T> { serializer.ConvertToType<T>(value) };
}
}
public static bool IsJsonArray(this object obj)
{
if (obj is string || obj is IDictionary)
return false;
return obj is IEnumerable;
}
public static IEnumerable<object> AsJsonArray(this object obj)
{
return (obj as IEnumerable).Cast<object>();
}
}
Then use it like:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new RootObjectConverter() });
var root = serializer.Deserialize<RootObject>(json);
I am getting an error with a Date in a JSON string: /Date(1370963229000)/ is not a valid value for DateTime., and I can fix this by doing a ToString("g") on the date, but I don't want to have to explicitly put every column in my select statement.
Currently, I am doing:
var people = _context.People.ToList();
I don't want to have to do var people = _context.People.Select({x=>x.Id.....});
Method 1: Use "Proxy" Properties
Put [ScriptIgnore] attributes on your DateTime properties and implement proxy properties that get the date value as a string. The properties with [ScriptIgnore] will be skipped by JavaScriptSerializer and the proxy properties will be emitted. For example:
[ScriptIgnore]
public DateTime DateValue { get; set; }
public string DateValueJS
{
get { return DateValue.ToString("g"); }
}
Method 2: Use CustomConverters with JavaScriptSerializer
Use the CustomConverters support that's built into JavaScriptSerializer to register your own class for handling the serialization of particular types. For example:
public class DateJsonConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new Type[] { typeof(DateTime) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
return new Dictionary<string, object>()
{
{ "Value", ((DateTime)obj).ToString("g") }
};
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
throw new NotSupportedException();
}
}
And you use this custom converter like this:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new DateJsonConverter() });
Date values will be serialized by this class into: {"Dt":{"Value":"6/11/2013 5:36 PM"}}
Method 3: Use Reflection to transparently format DateTime
You can use reflection to transparently convert DateTime values into string values when the value is being serialized. For example:
private static object FormatDateTime(object x)
{
if (x == null || x is IEnumerable)
return x;
var t = x.GetType();
if (t == typeof(DateTime))
return ((DateTime)x).ToString("g");
if (t.IsPrimitive)
return x;
var result = new Dictionary<string, object>();
foreach (var prop in t.GetProperties())
{
// Skip properties with ScriptIgnoreAttribute
if (prop.GetCustomAttributes(typeof(ScriptIgnoreAttribute), true).Any())
continue;
result[prop.Name] = FormatDateTime(prop.GetValue(x, null));
}
return result;
}
This method can be used in your Select statement to convert the object values into a Dictionary that JavaScriptSerializer can use to emit the JSON. For example:
var value = new[] { new { Dt = DateTime.Now, Childs = new[] { 1, 2, 3 } } };
serializer.Serialize(value.Select(x => FormatDateTime(x)))
Will emit [{"Dt":"6/12/2013 3:27 PM","Childs":[1,2,3]}]
I never used JavaScriptSerializer, but if you have any influence on how the data is being deserialized, I'd recommend deserializing this data field into a string and then have a property on the Person class that will return the value converted into a DateTime.
Is there a universal way to instruct the DataContractJsonSerializer to use UTC for dates? Otherwise, I have to add .ToUniversalTime() to all of my date instances. Is this possible? The reason is that date values are defaulting DateTimeKind.Local and adding offsets to the JSON result. Making the dates universal does the trick, but can it be done at a global level? Thanks.
There's no way to do that directly at the global level - primitive types (such as DateTime) can't be "surrogated". A possible workaround is to use some kind of reflection along with a surrogate to change the DateTime fields (or properties) in an object when it's being serialized, as shown in the example below.
public class StackOverflow_6100587_751090
{
public class MyType
{
public MyTypeWithDates d1;
public MyTypeWithDates d2;
}
public class MyTypeWithDates
{
public DateTime Start;
public DateTime End;
}
public class MySurrogate : IDataContractSurrogate
{
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public Type GetDataContractType(Type type)
{
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
return obj;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
}
public object GetObjectToSerialize(object obj, Type targetType)
{
return ReplaceLocalDateWithUTC(obj);
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
private object ReplaceLocalDateWithUTC(object obj)
{
if (obj == null) return null;
Type objType = obj.GetType();
foreach (var field in objType.GetFields())
{
if (field.FieldType == typeof(DateTime))
{
DateTime fieldValue = (DateTime)field.GetValue(obj);
if (fieldValue.Kind != DateTimeKind.Utc)
{
field.SetValue(obj, fieldValue.ToUniversalTime());
}
}
}
return obj;
}
}
public static void Test()
{
MemoryStream ms = new MemoryStream();
DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(MyType), null, int.MaxValue, true, new MySurrogate(), false);
MyType t = new MyType
{
d1 = new MyTypeWithDates { Start = DateTime.Now, End = DateTime.Now.AddMinutes(1) },
d2 = new MyTypeWithDates { Start = DateTime.Now.AddHours(1), End = DateTime.Now.AddHours(2) },
};
dcjs.WriteObject(ms, t);
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
}
}
I'm using JavaScriptSerializer to serialize some entity objects.
The problem is, many of the public properties contain null or default values. Is there any way to make JavaScriptSerializer exclude properties with null or default values?
I would like the resulting JSON to be less verbose.
FYI, if you'd like to go with the easier solution, here's what I used to accomplish this using a JavaScriptConverter implementation with the JavaScriptSerializer:
private class NullPropertiesConverter: JavaScriptConverter {
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) {
throw new NotImplementedException();
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) {
var jsonExample = new Dictionary<string, object >();
foreach(var prop in obj.GetType().GetProperties()) {
//check if decorated with ScriptIgnore attribute
bool ignoreProp = prop.IsDefined(typeof(ScriptIgnoreAttribute), true);
var value = prop.GetValue(obj, BindingFlags.Public, null, null, null);
if (value != null && !ignoreProp)
jsonExample.Add(prop.Name, value);
}
return jsonExample;
}
public override IEnumerable<Type> SupportedTypes {
get {
return GetType().Assembly.GetTypes();
}
}
}
and then to use it:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] {
new NullPropertiesConverter()
});
return serializer.Serialize(someObjectToSerialize);
The solution that worked for me:
The serialized class and properties would be decorated as follows:
[DataContract]
public class MyDataClass
{
[DataMember(Name = "LabelInJson", IsRequired = false)]
public string MyProperty { get; set; }
}
IsRequired was the key item.
The actual serialization could be done using DataContractJsonSerializer:
public static string Serialize<T>(T obj)
{
string returnVal = "";
try
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
returnVal = Encoding.Default.GetString(ms.ToArray());
}
}
catch (Exception /*exception*/)
{
returnVal = "";
//log error
}
return returnVal;
}
For the benefit of those who find this on google, note that nulls can be skipped natively during serialization with Newtonsoft.Json
var json = JsonConvert.SerializeObject(
objectToSerialize,
new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore});
You can implement a JavaScriptConverter and register it using the RegisterConverters method of JavaScriptSerializer.
Json.NET has options to automatically exclude null or default values.
This code is block null and default(0) values for numeric types
private class NullPropertiesConverter: JavaScriptConverter {
public override object Deserialize(IDictionary < string, object > dictionary, Type type, JavaScriptSerializer serializer) {
throw new NotImplementedException();
}
public override IDictionary < string, object > Serialize(object obj, JavaScriptSerializer serializer) {
var jsonExample = new Dictionary < string,
object > ();
foreach(var prop in obj.GetType().GetProperties()) {
//this object is nullable
var nullableobj = prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable < > );
//check if decorated with ScriptIgnore attribute
bool ignoreProp = prop.IsDefined(typeof(ScriptIgnoreAttribute), true);
var value = prop.GetValue(obj, System.Reflection.BindingFlags.Public, null, null, null);
int i;
//Object is not nullable and value=0 , it is a default value for numeric types
if (!(nullableobj == false && value != null && (int.TryParse(value.ToString(), out i) ? i : 1) == 0) && value != null && !ignoreProp)
jsonExample.Add(prop.Name, value);
}
return jsonExample;
}
public override IEnumerable < Type > SupportedTypes {
get {
return GetType().Assembly.GetTypes();
}
}
}
Without changing toe DataContractSerializer
You can use ScriptIgnoreAttribute
[1] http://msdn.microsoft.com/en-us/library/system.web.script.serialization.scriptignoreattribute.aspx