Skip to content

Commit

Permalink
[feat]FastJson序列化支持DateOnly和TimeOnly类型,得益于底层ChangeType的支持。NewLifeX/Ne…
Browse files Browse the repository at this point in the history
  • Loading branch information
nnhy committed May 25, 2024
1 parent bfdb5ee commit b40e0e6
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 206 deletions.
2 changes: 2 additions & 0 deletions NewLife.Core/Data/DbTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,8 @@ public async Task WriteXml(Stream stream)
writer.WriteValue(new DateTimeOffset(row[i].ChangeType<DateTime>()));
else if (ts[i] == typeof(DateTimeOffset))
writer.WriteValue(row[i].ChangeType<DateTimeOffset>());
else if (row[i] is IFormattable ft)
await writer.WriteStringAsync(ft + "");
else
await writer.WriteStringAsync(row[i] + "");

Expand Down
31 changes: 20 additions & 11 deletions NewLife.Core/Reflection/IReflect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -738,21 +738,30 @@ public virtual void Copy(Object target, IDictionary<String, Object?> source, Boo
// 支持DateTimeOffset转换
if (conversionType == typeof(DateTimeOffset)) return value.ToDateTimeOffset();

#if NET7_0_OR_GREATER
// 支持IParsable<TSelf>接口
if (value is String str &&
conversionType.GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IParsable<>)))
if (value is String str)
{
var mi = conversionType.GetMethod("Parse", [typeof(String), typeof(IFormatProvider)]);
if (mi != null)
{
//var func = mi.As<Func<String, IFormatProvider?, Object>>();
//return func(str, null);
// 特殊处理几种类型,避免后续反射影响性能
if (conversionType == typeof(Guid)) return Guid.Parse(str);
if (conversionType == typeof(TimeSpan)) return TimeSpan.Parse(str);
#if NET5_0_OR_GREATER
if (conversionType == typeof(IntPtr)) return IntPtr.Parse(str);
if (conversionType == typeof(UIntPtr)) return UIntPtr.Parse(str);
if (conversionType == typeof(Half)) return Half.Parse(str);
#endif
#if NET6_0_OR_GREATER
if (conversionType == typeof(DateOnly)) return DateOnly.Parse(str);
if (conversionType == typeof(TimeOnly)) return TimeOnly.Parse(str);
#endif

return mi.Invoke(null, [value, null]);
#if NET7_0_OR_GREATER
// 支持IParsable<TSelf>接口
if (conversionType.GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IParsable<>)))
{
var mi = conversionType.GetMethod("Parse", [typeof(String), typeof(IFormatProvider)]);
if (mi != null) return mi.Invoke(null, [value, null]);
}
}
#endif
}

if (value is IConvertible) value = Convert.ChangeType(value, conversionType);
}
Expand Down
43 changes: 38 additions & 5 deletions NewLife.Core/Security/Rand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,23 @@ public static T Fill<T>(T value)

foreach (var pi in value.GetType().GetProperties(true))
{
switch (pi.PropertyType.GetTypeCode())
// 可空类型,有一定记录填充null
var type = pi.PropertyType;
if (type.IsNullable())
{
// 10%几率填充null
if (Next(0, 10) == 0)
{
pi.SetValue(value, null);
continue;
}

type = Nullable.GetUnderlyingType(type) ?? type;
}

// 给基础类型填充数据
var code = type.GetTypeCode();
switch (code)
{
case TypeCode.Empty:
case TypeCode.Object:
Expand All @@ -148,19 +164,19 @@ public static T Fill<T>(T value)
pi.SetValue(value, Next(2) > 0);
break;
case TypeCode.Char:
pi.SetValue(value, (Char)Next(0, 256));
pi.SetValue(value, (Char)Next(Char.MinValue, Char.MaxValue));
break;
case TypeCode.SByte:
pi.SetValue(value, (SByte)Next(-128, 127));
pi.SetValue(value, (SByte)Next(SByte.MinValue, SByte.MaxValue));
break;
case TypeCode.Byte:
pi.SetValue(value, (Byte)Next(0, 256));
pi.SetValue(value, (Byte)Next(Byte.MinValue, Byte.MaxValue));
break;
case TypeCode.Int16:
pi.SetValue(value, (Int16)Next(Int16.MinValue, Int16.MaxValue));
break;
case TypeCode.UInt16:
pi.SetValue(value, (UInt16)Next(0, UInt16.MaxValue));
pi.SetValue(value, (UInt16)Next(UInt16.MinValue, UInt16.MaxValue));
break;
case TypeCode.Int32:
pi.SetValue(value, Next());
Expand Down Expand Up @@ -192,6 +208,23 @@ public static T Fill<T>(T value)
default:
break;
}

// 支持特殊类型
if (code == TypeCode.Object)
{
if (type == typeof(Guid))
pi.SetValue(value, Guid.NewGuid());
else if (type == typeof(DateTimeOffset))
pi.SetValue(value, new DateTimeOffset(new DateTime(2000, 1, 1).AddSeconds(Next(20 * 365 * 24 * 3600))));
else if (type == typeof(TimeSpan))
pi.SetValue(value, new TimeSpan(Next(20 * 24 * 3600 * 1000)));
#if NET6_0_OR_GREATER
else if (type == typeof(DateOnly))
pi.SetValue(value, new DateOnly(Next(1000, 2300), Next(1, 13), Next(1, 29)));
else if (type == typeof(TimeOnly))
pi.SetValue(value, new TimeOnly(Next(0, 24), Next(0, 60), Next(0, 60), Next(0, 1000)));
#endif
}
}

return value;
Expand Down
35 changes: 35 additions & 0 deletions NewLife.Core/Serialization/Binary/BinaryNormal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ public override Boolean Write(Object? value, Type type)

return true;
}
else if (type == typeof(DateTimeOffset) && value is DateTimeOffset dto)
{
Host.Write(dto.DateTime);
Host.Write(dto.Offset);
return true;
}
#if NET6_0_OR_GREATER
else if (type == typeof(DateOnly) && value is DateOnly date)
{
Host.Write(date.DayNumber);
return true;
}
else if (type == typeof(TimeOnly) && value is TimeOnly time)
{
Host.Write(time.Ticks);
return true;
}
#endif
else if (type == typeof(IPAddress) && value is IPAddress addr)
{
Host.Write(addr.GetAddressBytes());
Expand Down Expand Up @@ -112,6 +130,23 @@ public override Boolean TryRead(Type type, ref Object? value)
value = ReadChars(-1);
return true;
}
else if (type == typeof(DateTimeOffset))
{
value = new DateTimeOffset(Host.Read<DateTime>(), Host.Read<TimeSpan>());
return true;
}
#if NET6_0_OR_GREATER
else if (type == typeof(DateOnly))
{
value = DateOnly.FromDayNumber(Host.Read<Int32>());
return true;
}
else if (type == typeof(TimeOnly))
{
value = new TimeOnly(Host.Read<Int64>());
return true;
}
#endif
else if (type == typeof(IPAddress))
{
value = new IPAddress(ReadBytes(-1));
Expand Down
1 change: 1 addition & 0 deletions NewLife.Core/Serialization/Json/JsonReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ private Array ParseArray(IList<Object> list, Type type, Object? target)

if (type == typeof(TimeSpan)) return TimeSpan.Parse(value + "");

if (value is String str2) return str2.ChangeType(type);
if (type.IsBaseType()) return value.ChangeType(type);

return null;
Expand Down
19 changes: 11 additions & 8 deletions NewLife.Core/Serialization/Json/JsonWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,13 @@ UInt32 or UInt64
WriteDateTime(time);

else if (obj is DateTimeOffset offset)
WriteDateTime(offset);
_Builder.AppendFormat("\"{0:O}\"", offset);
#if NET6_0_OR_GREATER
else if (obj is DateOnly date)
_Builder.AppendFormat("\"{0:O}\"", date);
else if (obj is TimeOnly time2)
_Builder.AppendFormat("\"{0}\"", time2);
#endif

else if (obj is IDictionary<String, Object?> sdic)
WriteStringDictionary(sdic);
Expand Down Expand Up @@ -213,6 +219,10 @@ UInt32 or UInt64
WriteValue(obj.ToLong());
}

// 支持格式化的类型,有去有回
else if (obj is IFormattable)
WriteValue(obj + "");

else
WriteObject(obj);
}
Expand Down Expand Up @@ -275,13 +285,6 @@ private void WriteSD(StringDictionary dic)
_Builder.Append('}');
}

private void WriteDateTime(DateTimeOffset dateTimeOffset)
{
//2022-11-29T14:13:17.8763881+08:00
var str = dateTimeOffset.ToString("O");
_Builder.AppendFormat("\"{0}\"", str);
}

private void WriteDateTime(DateTime dateTime)
{
var dt = dateTime;
Expand Down
15 changes: 15 additions & 0 deletions NewLife.Core/Serialization/Xml/XmlGeneral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ public override Boolean Write(Object? value, Type type)
return true;
}

// 支持格式化的类型,有去有回
if (type.As<IFormattable>())
{
if (value is IFormattable ft) writer.WriteValue(ft + "");
return true;
}

return false;
}

Expand Down Expand Up @@ -230,6 +237,14 @@ public override Boolean TryRead(Type type, ref Object? value)
break;
}

#if NET7_0_OR_GREATER
if (type.GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IParsable<>)))
{
value = reader.ReadContentAsString().ChangeType(type);
return true;
}
#endif

return false;
}
}
64 changes: 8 additions & 56 deletions XUnitTest.Core/Serialization/FastJsonTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
using NewLife;
using NewLife.Log;
using NewLife.Model;
using NewLife.Security;
using NewLife.Serialization;
using Xunit;

namespace XUnitTest.Serialization;

public class FastJsonTest
public class FastJsonTest : JsonTestBase
{
FastJson _json = new FastJson();

Expand All @@ -33,63 +34,14 @@ public void Test1()
[Fact]
public void DateTimeTest()
{
var str = """
[
{
"ID": 0,
"Userid": 27,
"ClickTime": "2020-03-09T21:16:17.88",
"AdID": 39,
"AdAmount": 0.43,
"isGive": false,
"AdLinkUrl": "http://www.baidu.com",
"AdImgUrl": "/uploader/swiperPic/405621836.jpg",
"Type": "NewLife.Common.PinYin",
"Offset": "2022-11-29T14:13:17.8763881+08:00"
},
{
"ID": 0,
"Userid": 27,
"ClickTime": "2020-03-09T21:16:25.9052764+08:00",
"AdID": 40,
"AdAmount": 0.41,
"isGive": false,
"AdLinkUrl": "http://www.baidu.com",
"AdImgUrl": "/uploader/swiperPic/1978468752.jpg",
"Type": "String",
"Offset": "2022-11-29T14:13:17.8763881+08:00"
}
]
""";

var models = _json.Read(str, typeof(Model[])) as Model[];
Assert.Equal(2, models.Length);
var model = new Model();
Rand.Fill(model);
var js = _json.Write(model, true);

var m = models[0];
Assert.Equal(27, m.UserId);
Assert.Equal(new DateTime(2020, 3, 9, 21, 16, 17, 880), m.ClickTime);
Assert.Equal(39, m.AdId);
Assert.Equal(0.43, m.AdAmount);
Assert.False(m.IsGive);
Assert.Equal("http://www.baidu.com", m.AdLinkUrl);
Assert.Equal("/uploader/swiperPic/405621836.jpg", m.AdImgUrl);
Assert.Equal(typeof(NewLife.Common.PinYin), m.Type);
Assert.Equal(typeof(String), models[1].Type);
Assert.Equal(DateTimeOffset.Parse("2022-11-29T14:13:17.8763881+08:00"), m.Offset);
}
var models = _json.Read(_json_value, typeof(Model[])) as Model[];
Assert.Equal(2, models.Length);

class Model
{
public Int32 ID { get; set; }
public Int32 UserId { get; set; }
public DateTime ClickTime { get; set; }
public Int32 AdId { get; set; }
public Double AdAmount { get; set; }
public Boolean IsGive { get; set; }
public String AdLinkUrl { get; set; }
public String AdImgUrl { get; set; }
public Type Type { get; set; }
public DateTimeOffset Offset { get; set; }
CheckModel(models);
}

[Fact]
Expand Down
Loading

0 comments on commit b40e0e6

Please sign in to comment.