WCF DataContractJsonSerializer and setting DateTime objects to UTC? - c#

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()));
}
}

Related

How to add metadata to describe which properties are dates in JSON.Net

I would like to add a metadata property to my json so that the client side can know what properties are dates.
For example if I had an object like this:
{
"notADate": "a value",
"aDate": "2017-04-23T18:25:43.511Z",
"anotherDate": "2017-04-23T18:25:43.511Z"
}
I would like to add a metadata property to tell the consumer which properties to treat as dates something like this:
{
"_date_properties_": ["aDate", "anotherDate"],
"notADate": "a value",
"aDate": "2017-04-23T18:25:43.511Z",
"anotherDate": "2017-04-23T18:25:43.511Z"
}
Any help would be great, thanks!
You could create a custom ContractResolver that inserts a synthetic "_date_properties_" property into the contract of every object that is serialized.
To do this, first subclass DefaultContractResolver to allow contracts to be fluently customized after they have been created by application-added event handlers:
public class ConfigurableContractResolver : DefaultContractResolver
{
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Next, create an extension method to add the desired property to a JsonObjectContract:
public static class JsonContractExtensions
{
const string DatePropertiesName = "_date_properties_";
public static void AddDateProperties(this JsonContract contract)
{
var objectContract = contract as JsonObjectContract;
if (objectContract == null)
return;
var properties = objectContract.Properties.Where(p => p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(DateTime?)).ToList();
if (properties.Count > 0)
{
var property = new JsonProperty
{
DeclaringType = contract.UnderlyingType,
PropertyName = DatePropertiesName,
UnderlyingName = DatePropertiesName,
PropertyType = typeof(string[]),
ValueProvider = new FixedValueProvider(properties.Select(p => p.PropertyName).ToArray()),
AttributeProvider = new NoAttributeProvider(),
Readable = true,
Writable = false,
// Ensure // Ensure PreserveReferencesHandling and TypeNameHandling do not apply to the synthetic property.
ItemIsReference = false,
TypeNameHandling = TypeNameHandling.None,
};
objectContract.Properties.Insert(0, property);
}
}
class FixedValueProvider : IValueProvider
{
readonly object properties;
public FixedValueProvider(object value)
{
this.properties = value;
}
#region IValueProvider Members
public object GetValue(object target)
{
return properties;
}
public void SetValue(object target, object value)
{
throw new NotImplementedException("SetValue not implemented for fixed properties; set JsonProperty.Writable = false.");
}
#endregion
}
class NoAttributeProvider : IAttributeProvider
{
#region IAttributeProvider Members
public IList<Attribute> GetAttributes(Type attributeType, bool inherit) { return new Attribute[0]; }
public IList<Attribute> GetAttributes(bool inherit) { return new Attribute[0]; }
#endregion
}
}
Finally, serialize your example type as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
// Here I am using CamelCaseNamingStrategy as is shown in your JSON.
// If you don't want camel case, leave NamingStrategy null.
NamingStrategy = new CamelCaseNamingStrategy(),
}.Configure((s, e) => { e.Contract.AddDateProperties(); }),
};
var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);
This solution only handles statically typed DateTime and DateTime? properties. If you have object-valued properties that sometimes have DateTime values, or a Dictionary<string, DateTime>, or extension data containing DateTime values, you will need a more complex solution.
(As an alternative implementation, you could instead subclass DefaultContractResolver.CreateObjectContract and hardcode the required properties there using JsonContractExtensions.AddDateProperties(), however I thought it would be more interesting to create a general-purpose, fluently configurable contract resolver in case it becomes necessary to plug in different customizations later.)
You may want to cache the contract resolver for best performance.
Sample .Net fiddle.

How to add a property to a json object on the fly?

We are using Json.Net in our project to serialize and deserialize json objects.
Our entities have some DateTime properties and I would like to be able to convert them into PersianCalender DateTime and to provide them as string in my json object:
for example we have this entity :
public class PersonCertificate
{
public DateTime CertificateDate{get;set;}
}
I would like to have a json object like this :
{
"PersianCertificateDate":"1395/10/10"
}
So I thought that would be great to have an attribute named "AsPersianDate" for example so that I could do something like this:
public class PersonCertificate
{
[JsonIgnore]
[AsPersianDate]
public DateTime CertificateDate{get;set;}
}
I know that I can have a custom contract resolver to intercept json property creation process but I don't know how should I tell Json.Net to deserialize PersianCertificateDate into CertificateDate ?
OK it was far more easier than I thought.Actually ContractResolver is responsible for getting and setting all property values so here's what I have done:
public class EntityContractResolver:DefaultContractResolver
{
private class PersianDateValueProvider:IValueProvider
{
private readonly PropertyInfo _propertyInfo;
public PersianDateValueProvider(PropertyInfo propertyInfo)
{
_propertyInfo = propertyInfo;
}
public void SetValue(object target, object value)
{
try
{
var date = value as string;
if(value==null && _propertyInfo.PropertyType==typeof(DateTime))
throw new InvalidDataException();
_propertyInfo.SetValue(target,date.ToGregorianDate());
}
catch (InvalidDataException)
{
throw new ValidationException(new[]
{
new ValidationError
{
ErrorMessage = "Date is not valid",
FieldName = _propertyInfo.Name,
TypeName = _propertyInfo.DeclaringType.FullName
}
});
}
}
public object GetValue(object target)
{
if(_propertyInfo.PropertyType.IsNullable() && _propertyInfo.GetValue(target)==null) return null;
try
{
return ((DateTime) _propertyInfo.GetValue(target)).ToPersian();
}
catch
{
return string.Empty;
}
}
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var list= base.CreateProperties(type, memberSerialization).ToList();
list.AddRange(type.GetProperties()
.Where(pInfo => IsAttributeDefined(pInfo,typeof(AsPersianDateAttribute))&& (pInfo.PropertyType == typeof (DateTime) || pInfo.PropertyType == typeof (DateTime?)))
.Select(CreatePersianDateTimeProperty));
return list;
}
private JsonProperty CreatePersianDateTimeProperty(PropertyInfo propertyInfo)
{
return new JsonProperty
{
PropertyName = "Persian"+propertyInfo.Name ,
PropertyType = typeof (string),
ValueProvider = new PersianDateValueProvider(propertyInfo),
Readable = true,
Writable = true
};
}
private bool IsAttributeDefined(PropertyInfo propertyInfo,Type attribute)
{
var metaDataAttribute = propertyInfo.DeclaringType.GetCustomAttribute<MetadataTypeAttribute>(true);
var metaDataProperty = metaDataAttribute?.MetadataClassType?.GetProperty(propertyInfo.Name);
var metaDataHasAttribute = metaDataProperty != null && Attribute.IsDefined(metaDataProperty, attribute);
return metaDataHasAttribute || Attribute.IsDefined(propertyInfo, attribute);
}
}

Date cannot be serialized or deserialized

I want to serialize and deserialize Nullable DateTime to/from JSON but I do not want to annotate it with JsonConverterAttribute. However, I would like to keep it at once place in JsonSerializerSettings not bloating DTOs with those attributes keeping DTOs clean as usual.
Here is DTO:
public class Post
{
public DateTime? Created { get; set; }
}
Here is Custom JsonConverter:
internal class EpochDateTimeConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DateTime).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var t = (long)Convert.ToDouble(reader.Value.ToString());
return t.FromUnixTime();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
long ticks;
DateTime valueAsDate = (DateTime)value;
if (valueAsDate != DateTime.MinValue)
{
if (value is DateTime)
{
var epoc = new DateTime(1970, 1, 1);
var delta = (valueAsDate) - epoc;
if (delta.TotalSeconds < 0)
{
throw new ArgumentOutOfRangeException("Unix epoc starts January 1st, 1970");
}
ticks = (long)delta.TotalSeconds;
}
else
{
throw new Exception("Expected date object value.");
}
writer.WriteValue(ticks);
}
}
}
Here is the minimal repro:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace NameSpaceSample
{
public class Post
{
public DateTime? Created { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
Converters = new List<JsonConverter>
{
new EpochDateTimeConverter()
}
};
string postAsJson = JsonConvert.SerializeObject(new Post { Created = DateTime.UtcNow }, settings);
Console.WriteLine(postAsJson);// {"Created":"2015-09-17T17:15:06.6160689Z"}
var json = "{\"Created\":1442510191}";
Post post = JsonConvert.DeserializeObject<Post>(json, settings);//Exception here
Console.ReadKey();
}
}
}
The exception thrown at that line is:
JsonReaderException:
Error reading date. Unexpected token: Integer. Path 'Created', line 1, position 21.
NOTE:
I know this can be resolved by just annotating it with JsonConverterAttribute as below but I don't want to do that for aforementioned reason.
public class Post
{
[JsonConverter(typeof(EpochDateTimeConverter))]
public DateTime? Created { get; set; }
}
Figured it out on my own. I just had to change CanConvert function to following:
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
}
It is not very easy to find out. Putting my answer here to help others if they ever face this.
I give you my way from any object to another object so that you don't worry transfer anything ,thanks
public static T ConvertTo<T>(this object value)
{
T returnValue = default(T);
if (value is T)
{
returnValue = (T)value;
}
else
{
try
{
returnValue = (T)Convert.ChangeType(value, typeof(T));
}
catch (InvalidCastException)
{
returnValue = default(T);
}
}
return returnValue;
}
The CanConvert(Type objectType) method of the JsonConverter determines if that converter will be used for the current property that is being serialized/deserialized.
As the type of your property is DateTime? and that is not assignable from DateTime it returns false and the converter is then not being used.
You just need to change the method to the following:
public override bool CanConvert(Type objectType)
{
return typeof(DateTime?).IsAssignableFrom(objectType);
}

Nullable problems with Activator.CreateInstance and Convert.ChangeType

I have a third party library which sets a given objects property using reflection as follows. (This is the simplified version)
public void Set(object obj, string prop, object value) {
var propInf = obj.GetType().GetProperty(prop);
value = Convert.ChangeType(value, propInf.PropertyType);
propInf.SetValue(obj, value, null);
}
And We have class with a nullable property
class Test
{
public int? X { get; set; }
}
When I write the following code, it says it cannot convert int to int?
var t = new Test();
Set(t, "X", 1);
Since Nullable does not implement IConvertible it makes sense. Then I decided to write a method which returns the nullable version of a given value typed object.
public object MakeNullable(object obj) {
if(obj == null || !obj.GetType().IsValueType)
throw new Exception("obj must be value type!");
return Activator.CreateInstance(
typeof(Nullable<>).MakeGenericType(obj.GetType()),
new[] { obj });
}
I hoped to use this method as follows.
var t = new Test();
Set(t, "X", MakeNullable(1));
But it still says it cannot convert int to int?. When I debug typeof(Nullable<>).MakeGenericType(obj.GetType()) equals int? but Activator.CreateInstace returns an int value not int?
So this is my case... Any help?
This should work:
public static object ChangeType(object value, Type conversionType)
{
if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (value == null)
return null;
var nullableConverter = new NullableConverter(conversionType);
conversionType = nullableConverter.UnderlyingType;
}
return Convert.ChangeType(value, conversionType);
}
Try this code...
public void Set(object obj, string prop, object value)
{
//var propInf = obj.GetType().GetProperty(prop);
//value = Convert.ChangeType(value, propInf.PropertyType);
//propInf.SetValue(obj, value, null);
var property = obj.GetType().GetProperty(prop);
if (property != null)
{
Type t = Nullable.GetUnderlyingType(property.PropertyType)
?? property.PropertyType;
object safeValue = (value == null) ? null
: Convert.ChangeType(value, t);
property.SetValue(obj, safeValue, null);
}
}
from here
Convert.ChangeType() fails on Nullable Types
by
LukeH
UPDATE :
Hide base method.
namespace ConsoleApplication2
{
using System;
using System.Collections.Generic;
using System.Linq;
internal class Program
{
private static void Main(string[] args)
{
EnterPoint t = new EnterPoint();
t.BeginProcess();
}
}
public class EnterPoint
{
public void BeginProcess()
{
var t = new Test(); //Object
try
{
baseDllMethod m = new baseDllMethod(); //Your DLL
m.Set(t, "X", 1); //Problem Method, this give you error
}
catch (Exception ex)
{
Console.WriteLine("ERROR EN DLL METHOD");
}
xyz_D der = new xyz_D(); //Derivated method
der.Set(t, "X", 1) ; //HIDE BASE METHOD
}
}
public class baseDllMethod //YOUR DLL HERE
{
public void Set(object obj, string prop, object value)
{
var propInf = obj.GetType().GetProperty(prop);
value = Convert.ChangeType(value, propInf.PropertyType);
propInf.SetValue(obj, value, null);
}
}
public class xyz_D : baseDllMethod //Inherits
{
public new void Set(object obj, string prop, object value) //Hide base method
{
var property = obj.GetType().GetProperty(prop);
if (property != null)
{
Type t = Nullable.GetUnderlyingType(property.PropertyType)
?? property.PropertyType;
object safeValue = (value == null) ? null
: Convert.ChangeType(value, t);
property.SetValue(obj, safeValue, null);
}
}
}
public class Test //Your Object
{
public int? X { get; set; }
}
}

Why does JavaScriptSerializer ignore my converter?

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.

Categories

Resources