-Added JsonConverterAttribute. Can be placed on classes and members. Priority is member attribute -> class attribute -> serializer argument converters

-Added DateTimeFormat to IsoDateTimeConverter to customize format
-Changed IConvertible casting to the ToXXXX methods in JsonSerializer
This commit is contained in:
JamesNK
2008-08-04 09:38:24 +00:00
parent f2aa4a2b40
commit 7cb056d6c7
9 changed files with 356 additions and 41 deletions
@@ -70,6 +70,9 @@
<Compile Include="..\Newtonsoft.Json\JsonConverter.cs">
<Link>JsonConverter.cs</Link>
</Compile>
<Compile Include="..\Newtonsoft.Json\JsonConverterAttribute.cs">
<Link>JsonConverterAttribute.cs</Link>
</Compile>
<Compile Include="..\Newtonsoft.Json\JsonConverterCollection.cs">
<Link>JsonConverterCollection.cs</Link>
</Compile>
@@ -54,6 +54,24 @@ namespace Newtonsoft.Json.Tests.Converters
Assert.AreEqual(@"""2000-12-15T22:11:03.0550000" + d.GetLocalOffset() + @"""", result);
}
[Test]
public void SerializeFormattedDateTime()
{
IsoDateTimeConverter converter = new IsoDateTimeConverter() { DateTimeFormat = "F" };
DateTime d = new DateTime(2000, 12, 15, 22, 11, 3, 0, DateTimeKind.Utc);
string result;
result = JavaScriptConvert.SerializeObject(d, converter);
Assert.AreEqual(@"""Friday, 15 December 2000 22:11:03""", result);
Assert.AreEqual(d, JavaScriptConvert.DeserializeObject<DateTime>(result, converter));
d = new DateTime(2000, 12, 15, 22, 11, 3, 0, DateTimeKind.Local);
result = JavaScriptConvert.SerializeObject(d, converter);
Assert.AreEqual(@"""Friday, 15 December 2000 22:11:03""", result);
}
[Test]
public void SerializeDateTimeOffset()
{
@@ -37,6 +37,8 @@ using Newtonsoft.Json.Linq;
using System.Linq;
using Newtonsoft.Json.Converters;
using System.Runtime.Serialization.Json;
using Newtonsoft.Json.Utilities;
using System.Globalization;
namespace Newtonsoft.Json.Tests
{
@@ -1097,5 +1099,170 @@ keyword such as type of business.""
Assert.AreEqual("Param1", deserialized.Param1);
Assert.AreEqual("Param2", deserialized.Param2);
}
public class MemberConverterPrecedenceClassConverter : ConverterPrecedenceClassConverter
{
public override string ConverterType
{
get { return "Member"; }
}
}
public class ClassConverterPrecedenceClassConverter : ConverterPrecedenceClassConverter
{
public override string ConverterType
{
get { return "Class"; }
}
}
public class ArgumentConverterPrecedenceClassConverter : ConverterPrecedenceClassConverter
{
public override string ConverterType
{
get { return "Argument"; }
}
}
public abstract class ConverterPrecedenceClassConverter : JsonConverter
{
public abstract string ConverterType { get; }
public override void WriteJson(JsonWriter writer, object value)
{
ConverterPrecedenceClass c = (ConverterPrecedenceClass)value;
JToken j = new JArray(ConverterType, c.TestValue);
j.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType)
{
JToken j = JArray.Load(reader);
string converter = (string)j[0];
if (converter != ConverterType)
throw new Exception("Serialize converter {0} and deserialize converter {1} do not match.".FormatWith(CultureInfo.InvariantCulture, converter, ConverterType));
string testValue = (string)j[1];
return new ConverterPrecedenceClass(testValue);
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ConverterPrecedenceClass));
}
}
[JsonConverter(typeof(ClassConverterPrecedenceClassConverter))]
public class ConverterPrecedenceClass
{
public string TestValue { get; set; }
public ConverterPrecedenceClass(string testValue)
{
TestValue = testValue;
}
}
[Test]
public void SerializerShouldUseClassConverter()
{
ConverterPrecedenceClass c1 = new ConverterPrecedenceClass("!Test!");
string json = JavaScriptConvert.SerializeObject(c1);
Assert.AreEqual(@"[""Class"",""!Test!""]", json);
ConverterPrecedenceClass c2 = JavaScriptConvert.DeserializeObject<ConverterPrecedenceClass>(json);
Assert.AreEqual("!Test!", c2.TestValue);
}
[Test]
public void SerializerShouldUseClassConverterOverArgumentConverter()
{
ConverterPrecedenceClass c1 = new ConverterPrecedenceClass("!Test!");
string json = JavaScriptConvert.SerializeObject(c1, new ArgumentConverterPrecedenceClassConverter());
Assert.AreEqual(@"[""Class"",""!Test!""]", json);
ConverterPrecedenceClass c2 = JavaScriptConvert.DeserializeObject<ConverterPrecedenceClass>(json, new ArgumentConverterPrecedenceClassConverter());
Assert.AreEqual("!Test!", c2.TestValue);
}
public class MemberConverterClass
{
public DateTime DefaultConverter { get; set; }
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime MemberConverter { get; set; }
}
[Test]
public void SerializerShouldUseMemberConverter()
{
DateTime testDate = new DateTime(JavaScriptConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc);
MemberConverterClass m1 = new MemberConverterClass { DefaultConverter = testDate, MemberConverter = testDate };
string json = JavaScriptConvert.SerializeObject(m1);
Assert.AreEqual(@"{""DefaultConverter"":""\/Date(0)\/"",""MemberConverter"":""1970-01-01T00:00:00.0000000Z""}", json);
MemberConverterClass m2 = JavaScriptConvert.DeserializeObject<MemberConverterClass>(json);
Assert.AreEqual(testDate, m2.DefaultConverter);
Assert.AreEqual(testDate, m2.MemberConverter);
}
[Test]
public void SerializerShouldUseMemberConverterOverArgumentConverter()
{
DateTime testDate = new DateTime(JavaScriptConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc);
MemberConverterClass m1 = new MemberConverterClass { DefaultConverter = testDate, MemberConverter = testDate };
string json = JavaScriptConvert.SerializeObject(m1, new JavaScriptDateTimeConverter());
Assert.AreEqual(@"{""DefaultConverter"":new Date(0),""MemberConverter"":""1970-01-01T00:00:00.0000000Z""}", json);
MemberConverterClass m2 = JavaScriptConvert.DeserializeObject<MemberConverterClass>(json, new JavaScriptDateTimeConverter());
Assert.AreEqual(testDate, m2.DefaultConverter);
Assert.AreEqual(testDate, m2.MemberConverter);
}
public class ClassAndMemberConverterClass
{
public ConverterPrecedenceClass DefaultConverter { get; set; }
[JsonConverter(typeof(MemberConverterPrecedenceClassConverter))]
public ConverterPrecedenceClass MemberConverter { get; set; }
}
[Test]
public void SerializerShouldUseMemberConverterOverClassAndArgumentConverter()
{
ClassAndMemberConverterClass c1 = new ClassAndMemberConverterClass();
c1.DefaultConverter = new ConverterPrecedenceClass("DefaultConverterValue");
c1.MemberConverter = new ConverterPrecedenceClass("MemberConverterValue");
string json = JavaScriptConvert.SerializeObject(c1, new ArgumentConverterPrecedenceClassConverter());
Assert.AreEqual(@"{""DefaultConverter"":[""Class"",""DefaultConverterValue""],""MemberConverter"":[""Member"",""MemberConverterValue""]}", json);
ClassAndMemberConverterClass c2 = JavaScriptConvert.DeserializeObject<ClassAndMemberConverterClass>(json, new ArgumentConverterPrecedenceClassConverter());
Assert.AreEqual("DefaultConverterValue", c2.DefaultConverter.TestValue);
Assert.AreEqual("MemberConverterValue", c2.MemberConverter.TestValue);
}
[JsonConverter(typeof(IsoDateTimeConverter))]
public class IncompatibleJsonAttributeClass
{
}
[Test]
[ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "JsonConverter IsoDateTimeConverter on Newtonsoft.Json.Tests.JsonSerializerTest+IncompatibleJsonAttributeClass is not compatible with member type IncompatibleJsonAttributeClass.")]
public void IncompatibleJsonAttributeShouldThrow()
{
IncompatibleJsonAttributeClass c = new IncompatibleJsonAttributeClass();
JavaScriptConvert.SerializeObject(c);
}
}
}
@@ -12,9 +12,10 @@ namespace Newtonsoft.Json.Converters
/// </summary>
public class IsoDateTimeConverter : JsonConverter
{
private const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind;
private string _dateTimeFormat = DefaultDateTimeFormat;
/// <summary>
/// Gets or sets the date time styles used when converting a date to and from JSON.
@@ -26,6 +27,16 @@ namespace Newtonsoft.Json.Converters
set { _dateTimeStyles = value; }
}
/// <summary>
/// Gets or sets the date time format used when converting a date to and fromm JSON.
/// </summary>
/// <value>The date time format used when converting a date to and from JSON.</value>
public string DateTimeFormat
{
get { return _dateTimeFormat; }
set { _dateTimeFormat = value; }
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
@@ -43,7 +54,7 @@ namespace Newtonsoft.Json.Converters
|| (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
dateTime = dateTime.ToUniversalTime();
text = dateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
text = dateTime.ToString(_dateTimeFormat, CultureInfo.InvariantCulture);
}
else
{
@@ -52,7 +63,7 @@ namespace Newtonsoft.Json.Converters
|| (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
dateTimeOffset = dateTimeOffset.ToUniversalTime();
text = dateTimeOffset.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
text = dateTimeOffset.ToString(_dateTimeFormat, CultureInfo.InvariantCulture);
}
writer.WriteValue(text);
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Utilities;
using System.Globalization;
namespace Newtonsoft.Json
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)]
public class JsonConverterAttribute : Attribute
{
private readonly Type _converterType;
public Type ConverterType
{
get { return _converterType; }
}
public JsonConverterAttribute(Type converterType)
{
if (converterType == null)
throw new ArgumentNullException("converterType");
_converterType = converterType;
}
internal JsonConverter CreateJsonConverterInstance()
{
try
{
return (JsonConverter)Activator.CreateInstance(_converterType);
}
catch (Exception ex)
{
throw new Exception("Error creating {0}".FormatWith(CultureInfo.InvariantCulture, _converterType), ex);
}
}
}
}
+91 -37
View File
@@ -166,7 +166,7 @@ namespace Newtonsoft.Json
return null;
if (objectType != null)
return CreateObject(reader, objectType, null);
return CreateObject(reader, objectType, null, null);
else
return CreateJToken(reader);
}
@@ -195,14 +195,31 @@ namespace Newtonsoft.Json
return token;
}
private object CreateObject(JsonReader reader, Type objectType, object existingValue)
private bool HasClassConverter(Type objectType, out JsonConverter converter)
{
if (objectType == null)
throw new ArgumentNullException("objectType");
converter = GetConverter(objectType, objectType);
return (converter != null);
}
private object CreateObject(JsonReader reader, Type objectType, object existingValue, JsonConverter memberConverter)
{
_level++;
object value;
JsonConverter converter;
if (HasMatchingConverter(objectType, out converter))
if (memberConverter != null)
{
return memberConverter.ReadJson(reader, objectType);
}
else if (objectType != null && HasClassConverter(objectType, out converter))
{
return converter.ReadJson(reader, objectType);
}
else if (objectType != null && HasMatchingConverter(objectType, out converter))
{
return converter.ReadJson(reader, objectType);
}
@@ -338,7 +355,10 @@ namespace Newtonsoft.Json
bool readable = ReflectionUtils.CanReadMemberValue(member);
bool writable = ReflectionUtils.CanSetMemberValue(member);
MemberMapping memberMapping = new MemberMapping(mappedName, member, ignored, readable, writable);
JsonConverter memberConverter = GetConverter(member, ReflectionUtils.GetMemberUnderlyingType(member));
MemberMapping memberMapping = new MemberMapping(mappedName, member, ignored, readable, writable, memberConverter);
memberMappings.AddMapping(memberMapping);
}
@@ -366,6 +386,23 @@ namespace Newtonsoft.Json
return objectAttribute.MemberSerialization;
}
private JsonConverter GetConverter(ICustomAttributeProvider attributeProvider, Type targetConvertedType)
{
JsonConverterAttribute converterAttribute = ReflectionUtils.GetAttribute<JsonConverterAttribute>(attributeProvider, true);
if (converterAttribute != null)
{
JsonConverter memberConverter = converterAttribute.CreateJsonConverterInstance();
if (!memberConverter.CanConvert(targetConvertedType))
throw new JsonSerializationException("JsonConverter {0} on {1} is not compatible with member type {2}.".FormatWith(CultureInfo.InvariantCulture, memberConverter.GetType().Name, attributeProvider, targetConvertedType.Name));
return memberConverter;
}
return null;
}
private void SetObjectMember(JsonReader reader, object target, Type targetType, string memberName)
{
if (!reader.Read())
@@ -402,7 +439,7 @@ namespace Newtonsoft.Json
return;
}
value = CreateObject(reader, memberType, (useExistingValue) ? currentValue : null);
value = CreateObject(reader, memberType, (useExistingValue) ? currentValue : null, GetConverter(memberMapping.Member, memberType));
if (_nullValueHandling == NullValueHandling.Ignore && value == null)
return;
@@ -439,7 +476,7 @@ namespace Newtonsoft.Json
object keyValue = EnsureType(reader.Value, dictionaryKeyType);
reader.Read();
dictionary.Add(keyValue, CreateObject(reader, dictionaryValueType, null));
dictionary.Add(keyValue, CreateObject(reader, dictionaryValueType, null, null));
break;
case JsonToken.EndObject:
return dictionary;
@@ -467,7 +504,7 @@ namespace Newtonsoft.Json
case JsonToken.Comment:
break;
default:
object value = CreateObject(reader, listItemType, null);
object value = CreateObject(reader, listItemType, null, null);
list.Add(value);
break;
@@ -497,6 +534,7 @@ namespace Newtonsoft.Json
// create a dictionary to put retrieved values into
IDictionary<ParameterInfo, object> constructorParameters = c.GetParameters().ToDictionary(p => p, p => (object)null);
MemberMappingCollection mappings = GetMemberMappings(objectType);
bool exit = false;
while (!exit && reader.Read())
@@ -505,16 +543,21 @@ namespace Newtonsoft.Json
{
case JsonToken.PropertyName:
string memberName = reader.Value.ToString();
ParameterInfo matchingConstructorParameter = constructorParameters.ForgivingCaseSensitiveFind(kv => kv.Key.Name, memberName).Key;
if (!reader.Read())
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
if (matchingConstructorParameter != null)
constructorParameters[matchingConstructorParameter] = CreateObject(reader, matchingConstructorParameter.ParameterType, null);
else
reader.Skip();
ParameterInfo matchingConstructorParameter = constructorParameters.ForgivingCaseSensitiveFind(kv => kv.Key.Name, memberName).Key;
MemberMapping mapping;
if (matchingConstructorParameter != null && mappings.TryGetMapping(memberName, out mapping) && !mapping.Ignored)
{
constructorParameters[matchingConstructorParameter] = CreateObject(reader, matchingConstructorParameter.ParameterType, null, mapping.MemberConverter);
}
else
{
// skip token and all child tokens
reader.Skip();
}
break;
case JsonToken.EndObject:
exit = true;
@@ -578,11 +621,11 @@ namespace Newtonsoft.Json
if (value is JToken)
((JToken)value).WriteTo(jsonWriter, (_converters != null) ? _converters.ToArray() : null);
else
SerializeValue(jsonWriter, value);
SerializeValue(jsonWriter, value, null);
}
private void SerializeValue(JsonWriter writer, object value)
private void SerializeValue(JsonWriter writer, object value, JsonConverter memberConverter)
{
JsonConverter converter;
@@ -590,6 +633,14 @@ namespace Newtonsoft.Json
{
writer.WriteNull();
}
else if (memberConverter != null)
{
memberConverter.WriteJson(writer, value);
}
else if (HasClassConverter(value.GetType(), out converter))
{
converter.WriteJson(writer, value);
}
else if (HasMatchingConverter(value.GetType(), out converter))
{
converter.WriteJson(writer, value);
@@ -601,49 +652,49 @@ namespace Newtonsoft.Json
switch (convertible.GetTypeCode())
{
case TypeCode.String:
writer.WriteValue((string)convertible);
writer.WriteValue(convertible.ToString(CultureInfo.InvariantCulture));
break;
case TypeCode.Char:
writer.WriteValue((char)convertible);
writer.WriteValue(convertible.ToChar(CultureInfo.InvariantCulture));
break;
case TypeCode.Boolean:
writer.WriteValue((bool)convertible);
writer.WriteValue(convertible.ToBoolean(CultureInfo.InvariantCulture));
break;
case TypeCode.SByte:
writer.WriteValue((sbyte)convertible);
writer.WriteValue(convertible.ToSByte(CultureInfo.InvariantCulture));
break;
case TypeCode.Int16:
writer.WriteValue((short)convertible);
writer.WriteValue(convertible.ToInt16(CultureInfo.InvariantCulture));
break;
case TypeCode.UInt16:
writer.WriteValue((ushort)convertible);
writer.WriteValue(convertible.ToUInt16(CultureInfo.InvariantCulture));
break;
case TypeCode.Int32:
writer.WriteValue((int)convertible);
writer.WriteValue(convertible.ToInt32(CultureInfo.InvariantCulture));
break;
case TypeCode.Byte:
writer.WriteValue((byte)convertible);
writer.WriteValue(convertible.ToByte(CultureInfo.InvariantCulture));
break;
case TypeCode.UInt32:
writer.WriteValue((uint)convertible);
writer.WriteValue(convertible.ToUInt32(CultureInfo.InvariantCulture));
break;
case TypeCode.Int64:
writer.WriteValue((long)convertible);
writer.WriteValue(convertible.ToInt64(CultureInfo.InvariantCulture));
break;
case TypeCode.UInt64:
writer.WriteValue((ulong)convertible);
writer.WriteValue(convertible.ToUInt64(CultureInfo.InvariantCulture));
break;
case TypeCode.Single:
writer.WriteValue((float)convertible);
writer.WriteValue(convertible.ToSingle(CultureInfo.InvariantCulture));
break;
case TypeCode.Double:
writer.WriteValue((double)convertible);
writer.WriteValue(convertible.ToDouble(CultureInfo.InvariantCulture));
break;
case TypeCode.DateTime:
writer.WriteValue((DateTime)convertible);
writer.WriteValue(convertible.ToDateTime(CultureInfo.InvariantCulture));
break;
case TypeCode.Decimal:
writer.WriteValue((decimal)convertible);
writer.WriteValue(convertible.ToDecimal(CultureInfo.InvariantCulture));
break;
case TypeCode.DBNull:
writer.WriteNull();
@@ -688,15 +739,18 @@ namespace Newtonsoft.Json
return HasMatchingConverter(_converters, type, out matchingConverter);
}
internal static bool HasMatchingConverter(IList<JsonConverter> converters, Type type, out JsonConverter matchingConverter)
internal static bool HasMatchingConverter(IList<JsonConverter> converters, Type objectType, out JsonConverter matchingConverter)
{
if (objectType == null)
throw new ArgumentNullException("objectType");
if (converters != null)
{
for (int i = 0; i < converters.Count; i++)
{
JsonConverter converter = converters[i];
if (converter.CanConvert(type))
if (converter.CanConvert(objectType))
{
matchingConverter = converter;
return true;
@@ -708,7 +762,7 @@ namespace Newtonsoft.Json
return false;
}
private void WriteMemberInfoProperty(JsonWriter writer, object value, MemberInfo member, string propertyName)
private void WriteMemberInfoProperty(JsonWriter writer, object value, MemberInfo member, string propertyName, JsonConverter memberConverter)
{
if (!ReflectionUtils.IsIndexedProperty(member))
{
@@ -735,7 +789,7 @@ namespace Newtonsoft.Json
}
writer.WritePropertyName(propertyName ?? member.Name);
SerializeValue(writer, memberValue);
SerializeValue(writer, memberValue, memberConverter);
}
}
@@ -772,7 +826,7 @@ namespace Newtonsoft.Json
foreach (MemberMapping memberMapping in memberMappings)
{
if (!memberMapping.Ignored && memberMapping.Readable)
WriteMemberInfoProperty(writer, value, memberMapping.Member, memberMapping.MappingName);
WriteMemberInfoProperty(writer, value, memberMapping.Member, memberMapping.MappingName, memberMapping.MemberConverter);
}
writer.WriteEndObject();
@@ -796,7 +850,7 @@ namespace Newtonsoft.Json
for (int i = 0; i < values.Count; i++)
{
SerializeValue(writer, values[i]);
SerializeValue(writer, values[i], null);
}
writer.WriteEndArray();
@@ -809,7 +863,7 @@ namespace Newtonsoft.Json
foreach (DictionaryEntry entry in values)
{
writer.WritePropertyName(entry.Key.ToString());
SerializeValue(writer, entry.Value);
SerializeValue(writer, entry.Value, null);
}
writer.WriteEndObject();
+8 -1
View File
@@ -37,14 +37,16 @@ namespace Newtonsoft.Json
private readonly bool _ignored;
private readonly bool _readable;
private readonly bool _writable;
private readonly JsonConverter _memberConverter;
public MemberMapping(string mappingName, MemberInfo member, bool ignored, bool readable, bool writable)
public MemberMapping(string mappingName, MemberInfo member, bool ignored, bool readable, bool writable, JsonConverter memberConverter)
{
_mappingName = mappingName;
_member = member;
_ignored = ignored;
_readable = readable;
_writable = writable;
_memberConverter = memberConverter;
}
public string MappingName
@@ -71,5 +73,10 @@ namespace Newtonsoft.Json
{
get { return _writable; }
}
public JsonConverter MemberConverter
{
get { return _memberConverter; }
}
}
}
@@ -63,5 +63,19 @@ namespace Newtonsoft.Json
Add(memberMapping);
}
public bool TryGetMapping(string mappingName, out MemberMapping memberMapping)
{
if (Contains(mappingName))
{
memberMapping = this[mappingName];
return true;
}
else
{
memberMapping = default(MemberMapping);
return false;
}
}
}
}
@@ -74,6 +74,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="JsonConverterAttribute.cs" />
<Compile Include="JsonObjectAttribute.cs" />
<Compile Include="MemberSerialization.cs" />
<Compile Include="ObjectCreationHandling.cs" />