From fbc9e44028536ecabecf6318d980c2cfc26bde1d Mon Sep 17 00:00:00 2001 From: huqingjie Date: Fri, 2 Jun 2023 20:29:48 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DotNetCommon.Core/Data/Page.cs | 4 +- src/DotNetCommon.Core/Data/PageQuery.cs | 4 +- src/DotNetCommon.Core/Data/Result.cs | 8 +- src/DotNetCommon.Core/Data/ResultPage.cs | 4 +- src/DotNetCommon.Core/Data/TreeNode.cs | 4 +- src/DotNetCommon.Core/DeepCloneHelper.cs | 27 +-- .../DotNetCommon.Core.csproj | 5 +- .../Extensions/ObjectExtensions.cs | 65 +++--- .../Extensions/StringExtensions.cs | 60 ++++-- src/DotNetCommon.Core/JsonHelper.cs | 24 +-- src/DotNetCommon.Core/MapperHelper.cs | 8 +- .../Serialize/DateTimeConverter.cs | 49 +++-- .../Serialize/NumberConverter.cs | 172 ---------------- .../Serialize/PrimitiveConvertor.cs | 193 ------------------ src/DotNetCommon/RoslynMapper.cs | 3 +- .../Serialize/SerializeTests.cs | 4 +- 16 files changed, 159 insertions(+), 475 deletions(-) delete mode 100644 src/DotNetCommon.Core/Serialize/NumberConverter.cs delete mode 100644 src/DotNetCommon.Core/Serialize/PrimitiveConvertor.cs diff --git a/src/DotNetCommon.Core/Data/Page.cs b/src/DotNetCommon.Core/Data/Page.cs index b73c7a6..4b2c2a6 100644 --- a/src/DotNetCommon.Core/Data/Page.cs +++ b/src/DotNetCommon.Core/Data/Page.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Text; @@ -9,7 +8,6 @@ namespace DotNetCommon.Data /// /// 分页数据 /// - [JsonObject(MemberSerialization.OptOut)] [DataContract] public class Page { diff --git a/src/DotNetCommon.Core/Data/PageQuery.cs b/src/DotNetCommon.Core/Data/PageQuery.cs index 7ece93f..f884d01 100644 --- a/src/DotNetCommon.Core/Data/PageQuery.cs +++ b/src/DotNetCommon.Core/Data/PageQuery.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Text; @@ -9,7 +8,6 @@ namespace DotNetCommon.Data /// /// 分页查询请求 /// - [JsonObject(MemberSerialization.OptOut)] [DataContract] public class PageQuery { diff --git a/src/DotNetCommon.Core/Data/Result.cs b/src/DotNetCommon.Core/Data/Result.cs index dd3162a..d3a17fd 100644 --- a/src/DotNetCommon.Core/Data/Result.cs +++ b/src/DotNetCommon.Core/Data/Result.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -11,7 +10,6 @@ namespace DotNetCommon.Data /// /// 通用结果模型 /// - [JsonObject(MemberSerialization.OptOut)] [DataContract] public class Result { @@ -308,8 +306,8 @@ namespace DotNetCommon.Data Success = true, Data = t }; - var json = Newtonsoft.Json.JsonConvert.SerializeObject(value); - t = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + var json = System.Text.Json.JsonSerializer.Serialize(value); + t = System.Text.Json.JsonSerializer.Deserialize(json); return new Result() { Success = true, diff --git a/src/DotNetCommon.Core/Data/ResultPage.cs b/src/DotNetCommon.Core/Data/ResultPage.cs index c863893..a2f794c 100644 --- a/src/DotNetCommon.Core/Data/ResultPage.cs +++ b/src/DotNetCommon.Core/Data/ResultPage.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Text; @@ -9,7 +8,6 @@ namespace DotNetCommon.Data /// /// 返回分页结果数据 /// - [JsonObject(MemberSerialization.OptOut)] [DataContract] public class ResultPage : Result> { diff --git a/src/DotNetCommon.Core/Data/TreeNode.cs b/src/DotNetCommon.Core/Data/TreeNode.cs index 46b5880..f6070f1 100644 --- a/src/DotNetCommon.Core/Data/TreeNode.cs +++ b/src/DotNetCommon.Core/Data/TreeNode.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Text; @@ -10,7 +9,6 @@ namespace DotNetCommon.Data /// 树节点模型 /// /// 模型数据 - [JsonObject(MemberSerialization.OptOut)] [DataContract] public sealed class TreeNode { diff --git a/src/DotNetCommon.Core/DeepCloneHelper.cs b/src/DotNetCommon.Core/DeepCloneHelper.cs index 773eb80..e2faed1 100644 --- a/src/DotNetCommon.Core/DeepCloneHelper.cs +++ b/src/DotNetCommon.Core/DeepCloneHelper.cs @@ -1,6 +1,5 @@ using DotNetCommon.Data; using DotNetCommon.Extensions; -using Newtonsoft.Json.Linq; using System; using System.Collections; using System.Collections.Concurrent; @@ -184,19 +183,23 @@ namespace DotNetCommon } else if (reflect.Name == "Newtonsoft.Json.Linq.JObject") { - wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JObject).DeepClone(); - return wrapper; - } - else if (reflect.Name == "Newtonsoft.Json.Linq.JArray") - { - wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JArray).DeepClone(); - return wrapper; - } - else if (reflect.Name == "Newtonsoft.Json.Linq.JToken") - { - wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JToken).DeepClone(); + var tmp = Type.GetType("Newtonsoft.Json.Linq.JObject"); + var tmp2 = tmp.GetMethod("DeepClone"); + var tmp3 = Expression.Parameter(typeof(object), "obj"); + var act = Expression.Lambda>(Expression.Call(Expression.TypeAs(tmp3, tmp), tmp2), tmp3).Compile(); + wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); return wrapper; } + //else if (reflect.Name == "Newtonsoft.Json.Linq.JArray") + //{ + // wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JArray).DeepClone(); + // return wrapper; + //} + //else if (reflect.Name == "Newtonsoft.Json.Linq.JToken") + //{ + // wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JToken).DeepClone(); + // return wrapper; + //} else if (reflect.Name == "System.Collections.Generic.List") { GetCloneMethod_List(type, reflect, wrapper, tmpCache); diff --git a/src/DotNetCommon.Core/DotNetCommon.Core.csproj b/src/DotNetCommon.Core/DotNetCommon.Core.csproj index d46901c..7ab716c 100644 --- a/src/DotNetCommon.Core/DotNetCommon.Core.csproj +++ b/src/DotNetCommon.Core/DotNetCommon.Core.csproj @@ -1,7 +1,7 @@ - 3.2.0 + 4.0.0 True .net常用功能及数据模型,包含: @@ -44,7 +44,4 @@ \ - - - diff --git a/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs b/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs index fdddb76..deddb1e 100644 --- a/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs @@ -1,7 +1,4 @@ using DotNetCommon.Serialize; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.ComponentModel; @@ -19,6 +16,7 @@ using System.Threading.Tasks; using System.IO; using System.Collections; using static System.Net.Mime.MediaTypeNames; +using System.Text.Json; namespace DotNetCommon.Extensions { @@ -1131,12 +1129,27 @@ namespace DotNetCommon.Extensions /// 序列化为json字符串 /// /// - /// 序列化设置对象 + /// 序列化设置对象 /// - public static string ToJson(this object obj, JsonSerializerSettings settings = null) + public static string ToJson(this object obj, JsonSerializerOptions options = null) { if (obj == null) return null; - return Newtonsoft.Json.JsonConvert.SerializeObject(obj, settings); + if (options == null) + { + options = new JsonSerializerOptions + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + + }; + options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd")); + options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff")); + } + if (Environment.Version.Major == 6) + { + options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd")); + options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff")); + } + return System.Text.Json.JsonSerializer.Serialize(obj, options); } /// @@ -1144,42 +1157,36 @@ namespace DotNetCommon.Extensions /// /// /// 日期时间格式 - /// 是否将long型转为字符串 + /// 是否将数字转为字符串 /// 是否忽略null值的属性 /// 是否将枚举转换为字符串 /// 属性名称的首字母是否小写 /// 是否格式缩进 - /// 是否将所有数据类型转换为字符串 - /// 设定的所有数字的最大小数位数(默认为null,即: 不限制) - /// 仅针对decimal设定的最大小数位数(默认为null,即: 不限制) /// 其他的设置 /// - public static string ToJsonFast(this object obj, string dateFormatString = "yyyy-MM-dd HH:mm:ss", bool IgnoreNull = false, bool enum2String = true, bool lowerCamelCase = false, bool isIntend = false, bool isLongToString = false, bool isAllToString = false, int? allNumDigit = null, int? decimalDigit = null, Action otherSettings = null) + public static string ToJsonFast(this object obj, string dateFormatString = "yyyy-MM-dd HH:mm:ss", bool IgnoreNull = false, bool enum2String = true, bool lowerCamelCase = false, bool isIntend = false, bool number2String = false, Action otherSettings = null) { - var settings = new Newtonsoft.Json.JsonSerializerSettings() + var options = new JsonSerializerOptions() { - DateFormatString = dateFormatString, - Converters = new List() + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; - if (IgnoreNull) - { - settings.NullValueHandling = NullValueHandling.Ignore; - } - if (enum2String) - { - settings.Converters.Add(new StringEnumConverter()); - } - if (lowerCamelCase) + if (Environment.Version.Major == 6) { - settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd")); + options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff")); } - if (isIntend) + if (dateFormatString.IsNotNullOrEmptyOrWhiteSpace()) { - settings.Formatting = Formatting.Indented; + options.Converters.Add(new JsonConverterDatetime(dateFormatString)); + //options.Converters.Add(new JsonConverterDateTimeOffset(dateFormatString)); } - settings.Converters.Add(new PrimitiveConvertor(isLongToString, isAllToString, allNumDigit, decimalDigit)); - otherSettings?.Invoke(settings); - return JsonConvert.SerializeObject(obj, settings); + if (IgnoreNull) options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull; + if (enum2String) options.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); + if (lowerCamelCase) options.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; + if (isIntend) options.WriteIndented = true; + if (number2String) options.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.WriteAsString; + otherSettings?.Invoke(options); + return JsonSerializer.Serialize(obj, options); } #endregion diff --git a/src/DotNetCommon.Core/Extensions/StringExtensions.cs b/src/DotNetCommon.Core/Extensions/StringExtensions.cs index 93ba9da..31c2e03 100644 --- a/src/DotNetCommon.Core/Extensions/StringExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/StringExtensions.cs @@ -1,5 +1,4 @@ using DotNetCommon; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; @@ -7,7 +6,11 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; using System.Xml; @@ -425,23 +428,52 @@ namespace DotNetCommon.Extensions /// public static T ToObject(this string input, bool ignoreComment = false) { - if (ignoreComment && typeof(T) == typeof(JObject)) + if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JObject") { - var res = JObject.Parse(input, new JsonLoadSettings - { - CommentHandling = CommentHandling.Ignore - }).To(); - return res; + var act = getJObjectParseFunc(); + return (T)act(input, ignoreComment ? 1 : 0); } - if (ignoreComment && typeof(T) == typeof(JArray)) + if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JArray") { - var res = JArray.Parse(input, new JsonLoadSettings - { - CommentHandling = CommentHandling.Ignore - }).To(); - return res; + var act = getJArrayParseFunc(); + return (T)act(input, ignoreComment ? 1 : 0); + } + var options = new JsonSerializerOptions + { + AllowTrailingCommas = true, + ReadCommentHandling = System.Text.Json.JsonCommentHandling.Allow, + PropertyNameCaseInsensitive = true, + NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString, + }; + //TODO: T=JsonObject JsonArray + return JsonSerializer.Deserialize(input, options); + + Func getJObjectParseFunc() + { + var tmp = Type.GetType("Newtonsoft.Json.Linq.JObject"); + var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); + var tmp2 = Type.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); + var para = Expression.Parameter(typeof(string), "input"); + var para2 = Expression.Parameter(typeof(int)); + var ctor = tmp2.GetConstructors().FirstOrDefault(); + + var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), para2)); + var act = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); + return act; + } + Func getJArrayParseFunc() + { + var tmp = Type.GetType("Newtonsoft.Json.Linq.JArray"); + var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); + var tmp2 = Type.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); + var para = Expression.Parameter(typeof(string), "input"); + var para2 = Expression.Parameter(typeof(int)); + var ctor = tmp2.GetConstructors().FirstOrDefault(); + + var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), para2)); + var act = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); + return act; } - return Newtonsoft.Json.JsonConvert.DeserializeObject(input); } /// diff --git a/src/DotNetCommon.Core/JsonHelper.cs b/src/DotNetCommon.Core/JsonHelper.cs index bb51206..bc5042a 100644 --- a/src/DotNetCommon.Core/JsonHelper.cs +++ b/src/DotNetCommon.Core/JsonHelper.cs @@ -1,5 +1,5 @@ using DotNetCommon.Extensions; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace DotNetCommon { @@ -18,21 +18,17 @@ namespace DotNetCommon public static string Format(string srcJson, bool isIntend = true, bool removeComment = true) { if (srcJson.IsNullOrEmptyOrWhiteSpace()) return srcJson; - bool isArray = IsArray(srcJson); - if (isArray) + using var doc = JsonSerializer.Deserialize(srcJson, new JsonSerializerOptions { - return JArray.Parse(srcJson, new JsonLoadSettings - { - CommentHandling = removeComment ? CommentHandling.Ignore : CommentHandling.Load - }).ToString(isIntend ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None); - } - else + AllowTrailingCommas = true, + ReadCommentHandling = removeComment ? JsonCommentHandling.Skip : JsonCommentHandling.Allow, + }); + var res = JsonSerializer.Serialize(doc, new JsonSerializerOptions { - return JObject.Parse(srcJson, new JsonLoadSettings - { - CommentHandling = removeComment ? CommentHandling.Ignore : CommentHandling.Load - }).ToString(isIntend ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None); - } + WriteIndented = isIntend, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }); + return res; } /// diff --git a/src/DotNetCommon.Core/MapperHelper.cs b/src/DotNetCommon.Core/MapperHelper.cs index c3abf7b..8cf01bd 100644 --- a/src/DotNetCommon.Core/MapperHelper.cs +++ b/src/DotNetCommon.Core/MapperHelper.cs @@ -1,5 +1,4 @@ using DotNetCommon.Extensions; -using Newtonsoft.Json; using System; using System.Collections; using System.Collections.Concurrent; @@ -10,6 +9,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; +using System.Text.Json; using static System.Net.Mime.MediaTypeNames; namespace DotNetCommon @@ -667,12 +667,12 @@ namespace DotNetCommon if (cacheKey.dest.IsNullable()) { //Console.WriteLine($"JsonConvert IsNullable: {cacheKey.from.GetClassFullName()} => {cacheKey.dest.GetClassFullName()}"); - wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), cacheKey.dest); + wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonSerializer.Deserialize(JsonSerializer.Serialize(obj), cacheKey.dest); } else { //Console.WriteLine($"JsonConvert: {cacheKey.from.GetClassFullName()} => {cacheKey.dest.GetClassFullName()}"); - wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), cacheKey.dest); + wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : JsonSerializer.Deserialize(JsonSerializer.Serialize(obj), cacheKey.dest); } return wrapper; #endregion @@ -681,7 +681,7 @@ namespace DotNetCommon #region json字符串反序列化 if (cacheKey.from == typeof(string) && cacheKey.dest.IsClass) { - wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonConvert.DeserializeObject(obj.ToString(), cacheKey.dest); + wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonSerializer.Deserialize(obj.ToString(), cacheKey.dest); return wrapper; } #endregion diff --git a/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs b/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs index 7a4e74c..9abfe84 100644 --- a/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs +++ b/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs @@ -1,23 +1,46 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using System; -using System.Collections.Generic; -using System.Text; +using System; namespace DotNetCommon.Serialize { /// - /// 日期格式转换器,定义在属性上比较方便( [JsonConverter(typeof(DateTimeConverter), "yyyy-MM-dd")]) + /// 专为 .net6 提供 /// - public sealed class DateTimeConverter : IsoDateTimeConverter + public sealed class DateOnlyConverter : System.Text.Json.Serialization.JsonConverter { - /// - /// 构造函数 - /// - /// - public DateTimeConverter(string format) : base() + public string Format { get; set; } + public DateOnlyConverter() { - base.DateTimeFormat = format; + this.Format = "yyyy-MM-dd"; + } + public override DateOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) + { + return DateOnly.Parse(reader.GetString()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, DateOnly value, System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Format)); + } + } + + /// + /// 专为 .net6 提供 + /// + public sealed class TimeOnlyConverter : System.Text.Json.Serialization.JsonConverter + { + public string Format { get; set; } + public TimeOnlyConverter() + { + this.Format = "HH:mm:ss.fffffff"; + } + public override TimeOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) + { + return TimeOnly.Parse(reader.GetString()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, TimeOnly value, System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Format)); } } } diff --git a/src/DotNetCommon.Core/Serialize/NumberConverter.cs b/src/DotNetCommon.Core/Serialize/NumberConverter.cs deleted file mode 100644 index f490210..0000000 --- a/src/DotNetCommon.Core/Serialize/NumberConverter.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; - -namespace DotNetCommon.Serialize -{ - /// - /// 大数据类型序列化器,支持将long/float/double/decimal转成字符串,定义在属性上比较方便([JsonConverter(typeof(NumberConverter), NumberConverterShip.Int64)]) - /// - public sealed class NumberConverter : JsonConverter - { - /// - /// 转换成字符串的类型 - /// - private readonly NumberConverterShip _ship; - - /// - /// 大数据json序列化重写实例化 - /// - public NumberConverter() - { - _ship = (NumberConverterShip)0xFF; - } - - /// - /// 大数据json序列化重写实例化 - /// - /// 转换成字符串的类型 - public NumberConverter(NumberConverterShip ship) - { - _ship = ship; - } - - /// - /// - /// 确定此实例是否可以转换指定的对象类型。 - /// - /// 对象的类型。 - /// 如果此实例可以转换指定的对象类型,则为:true,否则为:false - public override bool CanConvert(Type objectType) - { - var typecode = Type.GetTypeCode(objectType.Name.Equals("Nullable`1") ? objectType.GetGenericArguments().First() : objectType); - switch (typecode) - { - case TypeCode.Decimal: - return (_ship & NumberConverterShip.Decimal) == NumberConverterShip.Decimal; - case TypeCode.Double: - return (_ship & NumberConverterShip.Double) == NumberConverterShip.Double; - case TypeCode.Int64: - return (_ship & NumberConverterShip.Int64) == NumberConverterShip.Int64; - case TypeCode.UInt64: - return (_ship & NumberConverterShip.UInt64) == NumberConverterShip.UInt64; - case TypeCode.Single: - return (_ship & NumberConverterShip.Single) == NumberConverterShip.Single; - default: return false; - } - } - - /// - /// - /// 读取对象的JSON表示。 - /// - /// 中读取。 - /// 对象的类型。 - /// 正在读取的对象的现有值。 - /// 调用的序列化器实例。 - /// 对象值。 - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return AsType(reader.Value.ToString(), objectType); - } - - /// - /// 字符串格式数据转其他类型数据 - /// - /// 输入的字符串 - /// 目标格式 - /// 转换结果 - public static object AsType(string input, Type destinationType) - { - try - { - var converter = TypeDescriptor.GetConverter(destinationType); - if (converter.CanConvertFrom(typeof(string))) - { - return converter.ConvertFrom(null, null, input); - } - - converter = TypeDescriptor.GetConverter(typeof(string)); - if (converter.CanConvertTo(destinationType)) - { - return converter.ConvertTo(null, null, input, destinationType); - } - } - catch - { - return null; - } - return null; - } - - /// - /// - /// 写入对象的JSON表示形式。 - /// - /// 要写入的 。 - /// 要写入对象值 - /// 调用的序列化器实例。 - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value == null) - { - writer.WriteNull(); - } - else - { - var objectType = value.GetType(); - var typeCode = Type.GetTypeCode(objectType.Name.Equals("Nullable`1") ? objectType.GetGenericArguments().First() : objectType); - switch (typeCode) - { - case TypeCode.Decimal: - writer.WriteValue(((decimal)value).ToString("f6")); - break; - case TypeCode.Double: - writer.WriteValue(((double)value).ToString("f4")); - break; - case TypeCode.Single: - writer.WriteValue(((float)value).ToString("f2")); - break; - default: - writer.WriteValue(value.ToString()); - break; - } - } - } - } - - /// - /// 转换成字符串的类型 - /// - [Flags] - public enum NumberConverterShip - { - /// - /// 长整数 - /// - Int64 = 1, - - /// - /// 无符号长整数 - /// - UInt64 = 2, - - /// - /// 浮点数 - /// - Single = 4, - - /// - /// 双精度浮点数 - /// - Double = 8, - - /// - /// 大数字 - /// - Decimal = 16 - } -} diff --git a/src/DotNetCommon.Core/Serialize/PrimitiveConvertor.cs b/src/DotNetCommon.Core/Serialize/PrimitiveConvertor.cs deleted file mode 100644 index fad7ade..0000000 --- a/src/DotNetCommon.Core/Serialize/PrimitiveConvertor.cs +++ /dev/null @@ -1,193 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; - -namespace DotNetCommon.Serialize -{ - /// - /// 接管c#基元类型转换(bool、char、byte、short/int/long/float/double/decimal - /// - public class PrimitiveConvertor : JsonConverter - { - private readonly bool isLongToString; - private readonly bool isAllToString; - private readonly int? allNumDigit; - private readonly int? decimalDigit; - - /// - /// 构造函数 - /// - /// 是否将所有支持的数据类型转换为字符串 - /// 是否将long类型转换为字符串 - /// 将所有的数字进行四舍五入后保留的小数位数 - /// 仅针对decimal数据类型进行四舍五入后保留的小数位数 - public PrimitiveConvertor(bool isLongToString = false, bool isAllToString = false, int? allNumDigit = null, int? decimalDigit = null) - { - this.isLongToString = isLongToString; - this.isAllToString = isAllToString; - this.allNumDigit = allNumDigit; - this.decimalDigit = decimalDigit; - } - - /// - /// 是否可以转换指定的数据类型 - /// - /// - /// - public override bool CanConvert(Type objectType) - { - var typecode = Type.GetTypeCode(objectType.Name.Equals("Nullable`1") ? objectType.GetGenericArguments().First() : objectType); - switch (typecode) - { - case TypeCode.Boolean: - case TypeCode.Char: - case TypeCode.SByte: - case TypeCode.Byte: - case TypeCode.Int16: - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - default: return false; - } - } - - /// - /// 反序列化json - /// - /// - /// - /// - /// - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return AsType(reader.Value?.ToString(), objectType); - } - - /// - /// 字符串格式数据转其他类型数据 - /// - /// 输入的字符串 - /// 目标格式 - /// 转换结果 - public static object AsType(string input, Type destinationType) - { - try - { - var converter = TypeDescriptor.GetConverter(destinationType); - if (converter.CanConvertFrom(typeof(string))) - { - return converter.ConvertFrom(null, null, input); - } - - converter = TypeDescriptor.GetConverter(typeof(string)); - if (converter.CanConvertTo(destinationType)) - { - return converter.ConvertTo(null, null, input, destinationType); - } - } - catch - { - return null; - } - return null; - } - - /// - /// 序列化指定数据类型 - /// - /// - /// - /// - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var typecode = Type.GetTypeCode(value?.GetType()); - switch (typecode) - { - case TypeCode.Boolean: - case TypeCode.Char: - case TypeCode.SByte: - case TypeCode.Byte: - case TypeCode.Int16: - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - { - if (isAllToString) writer.WriteValue(value?.ToString()); - else writer.WriteValue(value); - break; - } - case TypeCode.Int64: - case TypeCode.UInt64: - { - if (isAllToString || isLongToString) writer.WriteValue(value?.ToString()); - else - { - writer.WriteValue(value); - } - break; - } - case TypeCode.Single: - { - float val = (float)value; - if (allNumDigit != null) - { - val = float.Parse(val.ToString("F" + allNumDigit)); - } - if (isAllToString) writer.WriteValue(val.ToString()); - else - { - writer.WriteValue(val); - }; - break; - } - case TypeCode.Double: - { - double val = (double)value; - if (allNumDigit != null) - { - val = double.Parse(val.ToString("F" + allNumDigit)); - } - if (isAllToString) writer.WriteValue(val.ToString()); - else - { - writer.WriteValue(val); - }; - break; - } - case TypeCode.Decimal: - { - decimal val = (decimal)value; - if (decimalDigit != null) - { - val = decimal.Parse(val.ToString("F" + decimalDigit)); - } - else if (allNumDigit != null) - { - val = decimal.Parse(val.ToString("F" + allNumDigit)); - } - if (isAllToString) writer.WriteValue(val.ToString()); - else - { - writer.WriteValue(val); - }; - break; - } - default: - { - writer.WriteValue(value); - break; - } - } - } - } -} diff --git a/src/DotNetCommon/RoslynMapper.cs b/src/DotNetCommon/RoslynMapper.cs index 214ba5b..e941e70 100644 --- a/src/DotNetCommon/RoslynMapper.cs +++ b/src/DotNetCommon/RoslynMapper.cs @@ -2,7 +2,6 @@ using DotNetCommon.Extensions; using DotNetCommon.Logger; using DotNetCommon.Serialize; -using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -185,7 +184,7 @@ namespace DotNetCommon "System.IO", "System.Collections", "System.Collections.Generic", - "Newtonsoft.Json", + //"Newtonsoft.Json", "DotNetCommon.Serialize" }; diff --git a/tests/DotNetCommon.Test/Serialize/SerializeTests.cs b/tests/DotNetCommon.Test/Serialize/SerializeTests.cs index 259588f..e9c97e6 100644 --- a/tests/DotNetCommon.Test/Serialize/SerializeTests.cs +++ b/tests/DotNetCommon.Test/Serialize/SerializeTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Text; using DotNetCommon.Extensions; +using System.ComponentModel; namespace DotNetCommon.Test.Serialize { @@ -96,7 +97,8 @@ namespace DotNetCommon.Test.Serialize [JsonConverter(typeof(NumberConverter), NumberConverterShip.Int64)] public long Id { get; set; } - [JsonConverter(typeof(DateTimeConverter), "yyyy-MM-dd")] + //[JsonConverter(typeof(DateTimeConverter), "yyyy-MM-dd")] + [JsonConverter(typeof(DateOnlyConverter), "yyyy-MM-dd")] public DateTime Birth { get; set; } [JsonIgnore] -- Gitee From d2cb297252377865828ea3821e6e1cd514764e63 Mon Sep 17 00:00:00 2001 From: jackletter <1286317554@qq.com> Date: Sun, 4 Jun 2023 11:12:51 +0800 Subject: [PATCH 2/6] + --- src/DotNetCommon.Core/Data/Result.cs | 3 +- .../Extensions/ObjectExtensions.cs | 17 ++-- .../Extensions/StringExtensions.cs | 80 +++++++++++-------- .../Serialize/DateTimeConverter.cs | 59 +++++++++++++- .../Extensions/JsonExtensionTests.cs | 4 +- .../Extensions/StringExtensionsTests.cs | 6 +- .../Serialize/SerializeTests.cs | 12 +-- 7 files changed, 120 insertions(+), 61 deletions(-) diff --git a/src/DotNetCommon.Core/Data/Result.cs b/src/DotNetCommon.Core/Data/Result.cs index d3a17fd..be99793 100644 --- a/src/DotNetCommon.Core/Data/Result.cs +++ b/src/DotNetCommon.Core/Data/Result.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using System.Text.Json; using System.Threading.Tasks; using DotNetCommon.Extensions; @@ -121,7 +122,7 @@ namespace DotNetCommon.Data if (data == null) this.Data = default(T); else { - this.Data = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(data)); + this.Data = JsonSerializer.Deserialize(JsonSerializer.Serialize(data)); } } return this; diff --git a/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs b/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs index deddb1e..fd981f8 100644 --- a/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs @@ -10,7 +10,6 @@ using DotNetCommon.Logger; using System.Reflection; using System.Threading; using System.Collections.Concurrent; -using DateTimeConverter = DotNetCommon.Serialize.DateTimeConverter; using System.Collections.ObjectModel; using System.Threading.Tasks; using System.IO; @@ -411,13 +410,13 @@ namespace DotNetCommon.Extensions } #endregion #region 其他 使用 JsonConvert 实现 - return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), type); + return JsonSerializer.Deserialize(JsonSerializer.Serialize(value), type); #endregion } else { //兼容从json字符串反序列化 - if (value is string && type.IsClass) return JsonConvert.DeserializeObject(value.ToString(), type); + if (value is string && type.IsClass) return JsonSerializer.Deserialize(value.ToString(), type); //引用类型 return value; } @@ -1141,13 +1140,11 @@ namespace DotNetCommon.Extensions Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; - options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd")); - options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff")); } if (Environment.Version.Major == 6) { - options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd")); - options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff")); + options.Converters.Add(new JsonConverterDateOnly()); + options.Converters.Add(new JsonConverterTimeOnly()); } return System.Text.Json.JsonSerializer.Serialize(obj, options); } @@ -1172,13 +1169,13 @@ namespace DotNetCommon.Extensions }; if (Environment.Version.Major == 6) { - options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd")); - options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff")); + options.Converters.Add(new JsonConverterDateOnly()); + options.Converters.Add(new JsonConverterTimeOnly()); } if (dateFormatString.IsNotNullOrEmptyOrWhiteSpace()) { options.Converters.Add(new JsonConverterDatetime(dateFormatString)); - //options.Converters.Add(new JsonConverterDateTimeOffset(dateFormatString)); + options.Converters.Add(new JsonConverterDateTimeOffset(dateFormatString)); } if (IgnoreNull) options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull; if (enum2String) options.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); diff --git a/src/DotNetCommon.Core/Extensions/StringExtensions.cs b/src/DotNetCommon.Core/Extensions/StringExtensions.cs index 31c2e03..666162d 100644 --- a/src/DotNetCommon.Core/Extensions/StringExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/StringExtensions.cs @@ -419,6 +419,48 @@ namespace DotNetCommon.Extensions /// public static int GetSize(this string input) => input.Length * sizeof(char); + private static Func jobjectFunc = null; + private static Func getJObjectParseFunc(Type type) + { + if (jobjectFunc == null) + { + var assem = type.Assembly; + var tmp = assem.GetType("Newtonsoft.Json.Linq.JObject"); + var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); + var tmp2 = assem.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); + var para = Expression.Parameter(typeof(string), "input"); + var para2 = Expression.Parameter(typeof(int)); + var ctor = tmp2.GetConstructors().FirstOrDefault(); + //数字 枚举转换 + var enumType = assem.GetType("Newtonsoft.Json.Linq.CommentHandling"); + + var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(para2, enumType))); + jobjectFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); + } + return jobjectFunc; + } + + private static Func jarrFunc = null; + private static Func getJArrayParseFunc(Type type) + { + if (jarrFunc == null) + { + var assem = type.Assembly; + var tmp = assem.GetType("Newtonsoft.Json.Linq.JArray"); + var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); + var tmp2 = assem.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); + var para = Expression.Parameter(typeof(string), "input"); + var para2 = Expression.Parameter(typeof(int)); + var ctor = tmp2.GetConstructors().FirstOrDefault(); + //数字 枚举转换 + var enumType = assem.GetType("Newtonsoft.Json.Linq.CommentHandling"); + + var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(para2, enumType))); + jarrFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); + } + return jarrFunc; + } + /// /// 使用json的反序列化将字符串转为对象 /// @@ -430,50 +472,22 @@ namespace DotNetCommon.Extensions { if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JObject") { - var act = getJObjectParseFunc(); - return (T)act(input, ignoreComment ? 1 : 0); + var act = getJObjectParseFunc(typeof(T)); + return (T)act(input, ignoreComment ? 0 : 1); } if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JArray") { - var act = getJArrayParseFunc(); - return (T)act(input, ignoreComment ? 1 : 0); + var act = getJArrayParseFunc(typeof(T)); + return (T)act(input, ignoreComment ? 0 : 1); } var options = new JsonSerializerOptions { AllowTrailingCommas = true, - ReadCommentHandling = System.Text.Json.JsonCommentHandling.Allow, + ReadCommentHandling = System.Text.Json.JsonCommentHandling.Skip, PropertyNameCaseInsensitive = true, NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString, }; - //TODO: T=JsonObject JsonArray return JsonSerializer.Deserialize(input, options); - - Func getJObjectParseFunc() - { - var tmp = Type.GetType("Newtonsoft.Json.Linq.JObject"); - var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); - var tmp2 = Type.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); - var para = Expression.Parameter(typeof(string), "input"); - var para2 = Expression.Parameter(typeof(int)); - var ctor = tmp2.GetConstructors().FirstOrDefault(); - - var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), para2)); - var act = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); - return act; - } - Func getJArrayParseFunc() - { - var tmp = Type.GetType("Newtonsoft.Json.Linq.JArray"); - var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); - var tmp2 = Type.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); - var para = Expression.Parameter(typeof(string), "input"); - var para2 = Expression.Parameter(typeof(int)); - var ctor = tmp2.GetConstructors().FirstOrDefault(); - - var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), para2)); - var act = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); - return act; - } } /// diff --git a/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs b/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs index 9abfe84..4de83a4 100644 --- a/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs +++ b/src/DotNetCommon.Core/Serialize/DateTimeConverter.cs @@ -1,17 +1,23 @@ using System; +using System.ComponentModel; namespace DotNetCommon.Serialize { /// /// 专为 .net6 提供 /// - public sealed class DateOnlyConverter : System.Text.Json.Serialization.JsonConverter + public sealed class JsonConverterDateOnly : System.Text.Json.Serialization.JsonConverter { public string Format { get; set; } - public DateOnlyConverter() + public JsonConverterDateOnly() { this.Format = "yyyy-MM-dd"; } + public JsonConverterDateOnly(string format) + { + if (format == null) throw new ArgumentNullException("format"); + Format = format; + } public override DateOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { return DateOnly.Parse(reader.GetString()); @@ -26,21 +32,66 @@ namespace DotNetCommon.Serialize /// /// 专为 .net6 提供 /// - public sealed class TimeOnlyConverter : System.Text.Json.Serialization.JsonConverter + public sealed class JsonConverterTimeOnly : System.Text.Json.Serialization.JsonConverter { public string Format { get; set; } - public TimeOnlyConverter() + public JsonConverterTimeOnly() { this.Format = "HH:mm:ss.fffffff"; } + public JsonConverterTimeOnly(string format) + { + if (format == null) throw new ArgumentNullException("format"); + Format = format; + } public override TimeOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { return TimeOnly.Parse(reader.GetString()); } public override void Write(System.Text.Json.Utf8JsonWriter writer, TimeOnly value, System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Format)); + + } + } + + public class JsonConverterDatetime : System.Text.Json.Serialization.JsonConverter + { + public string Format { get; set; } + public JsonConverterDatetime(string format) + { + if (format == null) throw new ArgumentNullException("format"); + Format = format; + } + public override DateTime Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) + { + return DateTime.Parse(reader.GetString()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, DateTime value, System.Text.Json.JsonSerializerOptions options) { writer.WriteStringValue(value.ToString(Format)); } } + + public class JsonConverterDateTimeOffset : System.Text.Json.Serialization.JsonConverter + { + public string Format { get; set; } + public JsonConverterDateTimeOffset(string format) + { + if (format == null) throw new ArgumentNullException("format"); + Format = format; + } + public override DateTimeOffset Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()); + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, DateTimeOffset value, System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Format)); + } + } + } diff --git a/tests/DotNetCommon.Test/Extensions/JsonExtensionTests.cs b/tests/DotNetCommon.Test/Extensions/JsonExtensionTests.cs index 4880c8b..731e92f 100644 --- a/tests/DotNetCommon.Test/Extensions/JsonExtensionTests.cs +++ b/tests/DotNetCommon.Test/Extensions/JsonExtensionTests.cs @@ -23,10 +23,10 @@ namespace DotNetCommon.Test.Extensions Birth = DateTime.Now }; //{"Id":"1","Age":18,"Name":"小明","Birth":"2021-05-25 21:04:43"} - var json = person.ToJsonFast(isLongToString: true); + var json = person.ToJsonFast(number2String: true); //{"Id":"1","Age":"18","Name":"小明","Birth":"2021-05-25"} - json = person.ToJsonFast(isAllToString: true, dateFormatString: "yyyy-MM-dd"); + json = person.ToJsonFast(number2String: true, dateFormatString: "yyyy-MM-dd"); } diff --git a/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs b/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs index 94361a6..05de146 100644 --- a/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs +++ b/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using DotNetCommon.Extensions; using Newtonsoft.Json.Linq; +using System.Text.Json.Nodes; namespace DotNetCommon.Test.Extensions { @@ -577,11 +578,14 @@ namespace DotNetCommon.Test.Extensions "; var jArray = json.ToObject(); jArray.Count.ShouldBe(5); - jArray.Where(i => i.Type == JTokenType.Comment).Count().ShouldBe(2); + jArray.Where(i => i.Type == JTokenType.Comment).Count().ShouldBe(2); + jArray = json.ToObject(true); jArray.Count.ShouldBe(3); jArray.Where(i => i.Type == JTokenType.Comment).Count().ShouldBe(0); + + var jsonaArray = json.ToObject(); } } diff --git a/tests/DotNetCommon.Test/Serialize/SerializeTests.cs b/tests/DotNetCommon.Test/Serialize/SerializeTests.cs index e9c97e6..e28a925 100644 --- a/tests/DotNetCommon.Test/Serialize/SerializeTests.cs +++ b/tests/DotNetCommon.Test/Serialize/SerializeTests.cs @@ -81,24 +81,16 @@ namespace DotNetCommon.Test.Serialize }; var str = Newtonsoft.Json.JsonConvert.SerializeObject(person, Formatting.Indented); - var json = Newtonsoft.Json.JsonConvert.SerializeObject(person, Formatting.Indented, new JsonSerializerSettings() - { - Converters = new List() - { - new PrimitiveConvertor() - } - }); - str.ShouldBe(json); + + } #region Model1 & NumberConverter、DateTimeConverter public class Person { - [JsonConverter(typeof(NumberConverter), NumberConverterShip.Int64)] public long Id { get; set; } //[JsonConverter(typeof(DateTimeConverter), "yyyy-MM-dd")] - [JsonConverter(typeof(DateOnlyConverter), "yyyy-MM-dd")] public DateTime Birth { get; set; } [JsonIgnore] -- Gitee From 02798eaa5eeb8411b3b7044e3b26ff91d4dce371 Mon Sep 17 00:00:00 2001 From: jackletter <1286317554@qq.com> Date: Sun, 4 Jun 2023 15:35:15 +0800 Subject: [PATCH 3/6] =?UTF-8?q?ToObject=E6=94=B9=E9=80=A0=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/StringExtensions.cs | 26 +++++++--------- .../Extensions/StringExtensionsTests.cs | 30 +++++++++++++++---- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/DotNetCommon.Core/Extensions/StringExtensions.cs b/src/DotNetCommon.Core/Extensions/StringExtensions.cs index 666162d..b1c00f4 100644 --- a/src/DotNetCommon.Core/Extensions/StringExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/StringExtensions.cs @@ -419,8 +419,8 @@ namespace DotNetCommon.Extensions /// public static int GetSize(this string input) => input.Length * sizeof(char); - private static Func jobjectFunc = null; - private static Func getJObjectParseFunc(Type type) + private static Func jobjectFunc = null; + private static Func getJObjectParseFunc(Type type) { if (jobjectFunc == null) { @@ -429,19 +429,18 @@ namespace DotNetCommon.Extensions var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); var tmp2 = assem.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); var para = Expression.Parameter(typeof(string), "input"); - var para2 = Expression.Parameter(typeof(int)); var ctor = tmp2.GetConstructors().FirstOrDefault(); //数字 枚举转换 var enumType = assem.GetType("Newtonsoft.Json.Linq.CommentHandling"); - var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(para2, enumType))); - jobjectFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); + var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(Expression.Constant(0), enumType))); + jobjectFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para).Compile(); } return jobjectFunc; } - private static Func jarrFunc = null; - private static Func getJArrayParseFunc(Type type) + private static Func jarrFunc = null; + private static Func getJArrayParseFunc(Type type) { if (jarrFunc == null) { @@ -450,13 +449,11 @@ namespace DotNetCommon.Extensions var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); var tmp2 = assem.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); var para = Expression.Parameter(typeof(string), "input"); - var para2 = Expression.Parameter(typeof(int)); var ctor = tmp2.GetConstructors().FirstOrDefault(); //数字 枚举转换 var enumType = assem.GetType("Newtonsoft.Json.Linq.CommentHandling"); - - var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(para2, enumType))); - jarrFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para, para2).Compile(); + var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(Expression.Constant(0), enumType))); + jarrFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para).Compile(); } return jarrFunc; } @@ -466,19 +463,18 @@ namespace DotNetCommon.Extensions /// /// /// json字符串 - /// 是否忽略json中的注释 /// - public static T ToObject(this string input, bool ignoreComment = false) + public static T ToObject(this string input) { if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JObject") { var act = getJObjectParseFunc(typeof(T)); - return (T)act(input, ignoreComment ? 0 : 1); + return (T)act(input); } if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JArray") { var act = getJArrayParseFunc(typeof(T)); - return (T)act(input, ignoreComment ? 0 : 1); + return (T)act(input); } var options = new JsonSerializerOptions { diff --git a/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs b/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs index 05de146..35aff92 100644 --- a/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs +++ b/tests/DotNetCommon.Test/Extensions/StringExtensionsTests.cs @@ -7,6 +7,7 @@ using System.Text; using DotNetCommon.Extensions; using Newtonsoft.Json.Linq; using System.Text.Json.Nodes; +using System.Text.Json; namespace DotNetCommon.Test.Extensions { @@ -577,15 +578,34 @@ namespace DotNetCommon.Test.Extensions ] "; var jArray = json.ToObject(); - jArray.Count.ShouldBe(5); - jArray.Where(i => i.Type == JTokenType.Comment).Count().ShouldBe(2); - - - jArray = json.ToObject(true); jArray.Count.ShouldBe(3); jArray.Where(i => i.Type == JTokenType.Comment).Count().ShouldBe(0); + var jsonaArray = json.ToObject(); + jsonaArray.Count.ShouldBe(3); + + var jsonDoc = json.ToObject(); + jsonDoc.RootElement.ValueKind.ShouldBe(JsonValueKind.Array); + jsonDoc.RootElement.GetArrayLength().ShouldBe(3); + + var json2 = @" +{ + ""name"":""小明"", + ""age"":18, + ""birth"":""1998-02-01"", + //""score:"":98.5, + /*""ref"":null, + ""desc"":undefined*/ +} +"; + var jobj = json2.ToObject(); + jobj.Count.ShouldBe(3); + var jsonObject = json2.ToObject(); + jsonObject.Count.ShouldBe(3); + var jsonDocument = json2.ToObject(); + jsonDocument.RootElement.ValueKind.ShouldBe(JsonValueKind.Object); + jsonDocument.RootElement.EnumerateObject().Count().ShouldBe(3); } } -- Gitee From ac060ff7b297c5bdc5d27a0a1605e67e0a88a460 Mon Sep 17 00:00:00 2001 From: jackletter <1286317554@qq.com> Date: Sun, 4 Jun 2023 17:05:51 +0800 Subject: [PATCH 4/6] =?UTF-8?q?ToJson=20ToJsonFast=20=E6=94=B9=E9=80=A0?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/ObjectExtensions.cs | 83 ++++++++++++++++++- .../Extensions/StringExtensions.cs | 22 +++-- .../Serialize/SerializeTests.cs | 38 +++++++-- 3 files changed, 123 insertions(+), 20 deletions(-) diff --git a/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs b/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs index fd981f8..6bceac6 100644 --- a/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/ObjectExtensions.cs @@ -16,6 +16,7 @@ using System.IO; using System.Collections; using static System.Net.Mime.MediaTypeNames; using System.Text.Json; +using System.Linq.Expressions; namespace DotNetCommon.Extensions { @@ -1124,6 +1125,72 @@ namespace DotNetCommon.Extensions }; #region ToJson & ToJsonFast + private static Func _getNewtonsoftJsonSerialize = null; + private static Func getNewtonsoftJsonSerialize(Assembly assembly) + { + if (_getNewtonsoftJsonSerialize == null) + { + var tmp = assembly.GetType("Newtonsoft.Json.JsonConvert"); + var method = tmp.GetMethods().FirstOrDefault(i => i.Name == "SerializeObject" && i.GetParameters().Length == 1); + var para = Expression.Parameter(typeof(object), "obj"); + _getNewtonsoftJsonSerialize = Expression.Lambda>(Expression.Call(null, method, new Expression[] { para }), para).Compile(); + } + return _getNewtonsoftJsonSerialize; + } + + private static Func _getNewtonsoftJsonSerialize2 = null; + /// + /// 其实 JObject/JArray 转 json, 也就 isIntend 生效 + /// + /// + /// + private static Func getNewtonsoftJsonSerialize2(Assembly assembly) + { + if (_getNewtonsoftJsonSerialize2 == null) + { + var tmp = assembly.GetType("Newtonsoft.Json.JsonConvert"); + var tmp2 = assembly.GetType("Newtonsoft.Json.JsonSerializerSettings"); + var method = tmp.GetMethods().FirstOrDefault(i => i.Name == "SerializeObject" && i.GetParameters().Length == 2 && i.GetParameters().LastOrDefault().ParameterType == tmp2); + var para = Expression.Parameter(typeof(object), "obj"); + var para_DateFormatString = Expression.Parameter(typeof(string), "DateFormatString"); + var para_NullValueHandling = Expression.Parameter(typeof(bool), "NullValueHandling"); + var para_StringEnumConverter = Expression.Parameter(typeof(bool), "StringEnumConverter"); + var para_ContractResolver = Expression.Parameter(typeof(bool), "ContractResolver"); + var para_Formatting = Expression.Parameter(typeof(bool), "Formatting"); + + var local_settings = Expression.Variable(tmp2, "settings"); + var tmp3 = assembly.GetType("Newtonsoft.Json.NullValueHandling"); + var tmp4 = assembly.GetType("Newtonsoft.Json.Converters.StringEnumConverter"); + var tmp5 = assembly.GetType("Newtonsoft.Json.JsonConverter"); + var tmp6 = assembly.GetType("Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver"); + var tmp7 = assembly.GetType("Newtonsoft.Json.Formatting"); + + var assign_local = Expression.Assign(local_settings, Expression.New(tmp2.GetConstructor(new Type[0]))); + var assign_DateFormatString = Expression.IfThen(Expression.NotEqual(para_DateFormatString, Expression.Constant(null)), Expression.Assign(Expression.MakeMemberAccess(local_settings, tmp2.GetProperty("DateFormatString")), para_DateFormatString)); + var assign_NullValueHandling = Expression.IfThen(Expression.Equal(para_NullValueHandling, Expression.Constant(true)), Expression.Assign(Expression.MakeMemberAccess(local_settings, tmp2.GetProperty("NullValueHandling")), Expression.Convert(Expression.Constant(1), tmp3))); + + //var addMethod = typeof(List<>).MakeGenericType(tmp5).GetMethod("Add"); + var addMethod = typeof(ICollection<>).MakeGenericType(tmp5).GetMethod("Add"); + var assign_StringEnumConverter = Expression.IfThen(Expression.Equal(para_StringEnumConverter, Expression.Constant(true)), Expression.Call(Expression.Property(local_settings, "Converters"), addMethod, Expression.New(tmp4))); + var assign_ContractResolver = Expression.IfThen(Expression.Equal(para_ContractResolver, Expression.Constant(true)), Expression.Assign(Expression.Property(local_settings, "ContractResolver"), Expression.New(tmp6.GetConstructor(new Type[0])))); + var assign_Formatting = Expression.IfThen(Expression.Equal(para_Formatting, Expression.Constant(true)), Expression.Assign(Expression.Property(local_settings, "Formatting"), Expression.Convert(Expression.Constant(1), tmp7))); + + var finalCall = Expression.Call(null, method, para, local_settings); + + _getNewtonsoftJsonSerialize2 = Expression.Lambda>(Expression.Block(new ParameterExpression[] { local_settings }, + assign_local, + assign_DateFormatString, + assign_NullValueHandling, + assign_StringEnumConverter, + assign_ContractResolver, + assign_Formatting, + finalCall) + , para, para_DateFormatString, para_NullValueHandling, para_StringEnumConverter, para_ContractResolver, para_Formatting + ).Compile(); + } + return _getNewtonsoftJsonSerialize2; + } + /// /// 序列化为json字符串 /// @@ -1133,12 +1200,17 @@ namespace DotNetCommon.Extensions public static string ToJson(this object obj, JsonSerializerOptions options = null) { if (obj == null) return null; + var fullName = obj.GetType().GetClassFullName(); + if (fullName == "Newtonsoft.Json.Linq.JObject" || fullName == "Newtonsoft.Json.Linq.JArray") + { + var act = getNewtonsoftJsonSerialize(obj.GetType().Assembly); + return act(obj); + } if (options == null) { options = new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; } if (Environment.Version.Major == 6) @@ -1161,8 +1233,15 @@ namespace DotNetCommon.Extensions /// 是否格式缩进 /// 其他的设置 /// - public static string ToJsonFast(this object obj, string dateFormatString = "yyyy-MM-dd HH:mm:ss", bool IgnoreNull = false, bool enum2String = true, bool lowerCamelCase = false, bool isIntend = false, bool number2String = false, Action otherSettings = null) + public static string ToJsonFast(this object obj, string dateFormatString = null, bool IgnoreNull = false, bool enum2String = false, bool lowerCamelCase = false, bool isIntend = false, bool number2String = false, Action otherSettings = null) { + if (obj == null) return null; + var fullName = obj.GetType().GetClassFullName(); + if (fullName == "Newtonsoft.Json.Linq.JObject" || fullName == "Newtonsoft.Json.Linq.JArray") + { + var act = getNewtonsoftJsonSerialize2(obj.GetType().Assembly); + return act(obj, dateFormatString, IgnoreNull, enum2String, lowerCamelCase, isIntend); + } var options = new JsonSerializerOptions() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, diff --git a/src/DotNetCommon.Core/Extensions/StringExtensions.cs b/src/DotNetCommon.Core/Extensions/StringExtensions.cs index b1c00f4..73bbbac 100644 --- a/src/DotNetCommon.Core/Extensions/StringExtensions.cs +++ b/src/DotNetCommon.Core/Extensions/StringExtensions.cs @@ -420,18 +420,17 @@ namespace DotNetCommon.Extensions public static int GetSize(this string input) => input.Length * sizeof(char); private static Func jobjectFunc = null; - private static Func getJObjectParseFunc(Type type) + private static Func getJObjectParseFunc(Assembly assembly) { if (jobjectFunc == null) { - var assem = type.Assembly; - var tmp = assem.GetType("Newtonsoft.Json.Linq.JObject"); + var tmp = assembly.GetType("Newtonsoft.Json.Linq.JObject"); var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); - var tmp2 = assem.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); + var tmp2 = assembly.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); var para = Expression.Parameter(typeof(string), "input"); var ctor = tmp2.GetConstructors().FirstOrDefault(); //数字 枚举转换 - var enumType = assem.GetType("Newtonsoft.Json.Linq.CommentHandling"); + var enumType = assembly.GetType("Newtonsoft.Json.Linq.CommentHandling"); var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(Expression.Constant(0), enumType))); jobjectFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para).Compile(); @@ -440,18 +439,17 @@ namespace DotNetCommon.Extensions } private static Func jarrFunc = null; - private static Func getJArrayParseFunc(Type type) + private static Func getJArrayParseFunc(Assembly assembly) { if (jarrFunc == null) { - var assem = type.Assembly; - var tmp = assem.GetType("Newtonsoft.Json.Linq.JArray"); + var tmp = assembly.GetType("Newtonsoft.Json.Linq.JArray"); var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2); - var tmp2 = assem.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); + var tmp2 = assembly.GetType("Newtonsoft.Json.Linq.JsonLoadSettings"); var para = Expression.Parameter(typeof(string), "input"); var ctor = tmp2.GetConstructors().FirstOrDefault(); //数字 枚举转换 - var enumType = assem.GetType("Newtonsoft.Json.Linq.CommentHandling"); + var enumType = assembly.GetType("Newtonsoft.Json.Linq.CommentHandling"); var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(Expression.Constant(0), enumType))); jarrFunc = Expression.Lambda>(Expression.Call(null, parseMehtod, para, memberInit), para).Compile(); } @@ -468,12 +466,12 @@ namespace DotNetCommon.Extensions { if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JObject") { - var act = getJObjectParseFunc(typeof(T)); + var act = getJObjectParseFunc(typeof(T).Assembly); return (T)act(input); } if (typeof(T).GetClassFullName() == "Newtonsoft.Json.Linq.JArray") { - var act = getJArrayParseFunc(typeof(T)); + var act = getJArrayParseFunc(typeof(T).Assembly); return (T)act(input); } var options = new JsonSerializerOptions diff --git a/tests/DotNetCommon.Test/Serialize/SerializeTests.cs b/tests/DotNetCommon.Test/Serialize/SerializeTests.cs index e28a925..f8dcb7a 100644 --- a/tests/DotNetCommon.Test/Serialize/SerializeTests.cs +++ b/tests/DotNetCommon.Test/Serialize/SerializeTests.cs @@ -8,6 +8,8 @@ using System.Collections.Generic; using System.Text; using DotNetCommon.Extensions; using System.ComponentModel; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; namespace DotNetCommon.Test.Serialize { @@ -25,10 +27,13 @@ namespace DotNetCommon.Test.Serialize Id = long.MaxValue, Name = "小明", State = EnumState.Close, - Birth = DateTime.Now, + Birth = DateTime.Parse("2023-06-04 15:52:01.1234567 +08:00"), Addr = "天明路" }; var str = obj.ToJson(); + str.ShouldBe("{\"Id\":9223372036854775807,\"Birth\":\"2023-06-04T15:52:01.1234567+08:00\",\"Addr\":\"天明路\",\"Name\":\"小明\",\"StudentId\":0,\"ClassId\":null,\"State\":1}"); + var str2 = str.ToObject().ToJson(); + str2.ShouldBe(str); } /// @@ -76,13 +81,34 @@ namespace DotNetCommon.Test.Serialize PString = "abc", - PDateTime = DateTime.Now, - PDateTimeNull = DateTime.UtcNow, + PDateTime = DateTime.Parse("2023-06-04 16:40:17"), + PDateTimeNull = DateTime.Parse("2023-06-04 16:40:17"), }; - var str = Newtonsoft.Json.JsonConvert.SerializeObject(person, Formatting.Indented); - - + var json = person.ToJsonFast(); + json.ShouldBe("{\"PByte\":1,\"PByteNull\":2,\"PSByte\":-1,\"PSByteNull\":-2,\"PShort\":3,\"PShortNull\":4,\"PUShort\":5,\"PUShortNull\":6,\"PInt\":7,\"PUInt\":8,\"PIntNull\":9,\"PUIntNull\":10,\"PLong\":11,\"PULong\":12,\"PLongNull\":13,\"PULongNull\":14,\"PFloat\":15.1,\"PUFloat\":16.123457,\"PDouble\":17.123456789,\"PDoubleNull\":18.123456789012344,\"PDecimal\":19.12345678901234567890,\"PDecimalNull\":20.123456789012345678901234568,\"PChar\":\"a\",\"PCharNull\":\"b\",\"PBool\":false,\"PBoolNull\":null,\"PString\":\"abc\",\"PDateTime\":\"2023-06-04T16:40:17\",\"PDateTimeNull\":\"2023-06-04T16:40:17\",\"State\":0}"); + + var obj = new TestJObject + { + Id = 1, + Name = "小明", + BirthDay = DateTime.Parse("2023-06-04 16:43:01"), + State = EnumState.Open + }; + + json = obj.ToJson(); + json.ShouldBe("{\"Id\":1,\"Name\":\"小明\",\"BirthDay\":\"2023-06-04T16:43:01\",\"NullObj\":null,\"State\":2}"); + var json2 = json.ToObject().ToJsonFast(dateFormatString: "yyyy-MM-dd", IgnoreNull: true, enum2String: true, lowerCamelCase: true, isIntend: true); + json2.ShouldBe("{\r\n \"Id\": 1,\r\n \"Name\": \"小明\",\r\n \"BirthDay\": \"2023-06-04\",\r\n \"NullObj\": null,\r\n \"State\": 2\r\n}"); + } + + public class TestJObject + { + public int Id { get; set; } + public string Name { get; set; } + public DateTime BirthDay { get; set; } + public object NullObj { get; set; } + public EnumState State { get; set; } } #region Model1 & NumberConverter、DateTimeConverter -- Gitee From a1597370e8cbf0a0c36acbc0058814a2d15e24b6 Mon Sep 17 00:00:00 2001 From: jackletter <1286317554@qq.com> Date: Sun, 4 Jun 2023 17:40:11 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E5=AE=8C=E6=88=90=20Json?= =?UTF-8?q?Object/JsonArray/JsonDocument=20=E7=9A=84DeepClone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DotNetCommon.Core/DeepCloneHelper.cs | 44 ++++++++++++++----- .../Extensions/ObjectTests_DeepClone.cs | 30 +++++++++++++ 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/DotNetCommon.Core/DeepCloneHelper.cs b/src/DotNetCommon.Core/DeepCloneHelper.cs index e2faed1..b739aa1 100644 --- a/src/DotNetCommon.Core/DeepCloneHelper.cs +++ b/src/DotNetCommon.Core/DeepCloneHelper.cs @@ -9,6 +9,9 @@ using System.Linq.Expressions; using System.Numerics; using System.Reflection; using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; namespace DotNetCommon { @@ -183,23 +186,42 @@ namespace DotNetCommon } else if (reflect.Name == "Newtonsoft.Json.Linq.JObject") { - var tmp = Type.GetType("Newtonsoft.Json.Linq.JObject"); + var tmp = type.Assembly.GetType("Newtonsoft.Json.Linq.JObject"); var tmp2 = tmp.GetMethod("DeepClone"); var tmp3 = Expression.Parameter(typeof(object), "obj"); var act = Expression.Lambda>(Expression.Call(Expression.TypeAs(tmp3, tmp), tmp2), tmp3).Compile(); wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); return wrapper; } - //else if (reflect.Name == "Newtonsoft.Json.Linq.JArray") - //{ - // wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JArray).DeepClone(); - // return wrapper; - //} - //else if (reflect.Name == "Newtonsoft.Json.Linq.JToken") - //{ - // wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : (obj as JToken).DeepClone(); - // return wrapper; - //} + else if (reflect.Name == "Newtonsoft.Json.Linq.JArray") + { + var tmp = type.Assembly.GetType("Newtonsoft.Json.Linq.JArray"); + var tmp2 = tmp.GetMethod("DeepClone"); + var tmp3 = Expression.Parameter(typeof(object), "obj"); + var act = Expression.Lambda>(Expression.Call(Expression.TypeAs(tmp3, tmp), tmp2), tmp3).Compile(); + wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); + return wrapper; + } + #region TODO JsonObject/JsonArray/JsonDocument 原生没有提供clone方法 + else if (reflect.Name == "System.Text.Json.Nodes.JsonObject") + { + Func act = (obj) => JsonObject.Parse((obj as JsonObject).ToJsonString()); + wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); + return wrapper; + } + else if (reflect.Name == "System.Text.Json.Nodes.JsonArray") + { + Func act = (obj) => JsonArray.Parse((obj as JsonArray).ToJsonString()); + wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); + return wrapper; + } + else if (reflect.Name == "System.Text.Json.JsonDocument") + { + Func act = (obj) => JsonDocument.Parse(JsonSerializer.Serialize(obj as JsonDocument)); + wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); + return wrapper; + } + #endregion else if (reflect.Name == "System.Collections.Generic.List") { GetCloneMethod_List(type, reflect, wrapper, tmpCache); diff --git a/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs b/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs index edd3a00..ce0625e 100644 --- a/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs +++ b/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs @@ -13,6 +13,8 @@ using System.Diagnostics; using System.Numerics; using System.ComponentModel; using System.Drawing; +using System.Text.Json.Nodes; +using System.Text.Json; namespace DotNetCommon.Test.Extensions { @@ -27,6 +29,34 @@ namespace DotNetCommon.Test.Extensions objNew.ShouldBeNull(); } + [Test] + public void Test_JObjectJArray() + { + var obj = new { Id = 1, Name = "小明" }.ToJson().ToObject() as object; + var obj2 = obj.DeepClone(); + Assert.IsFalse(obj2 == obj); + + var arr = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject() as object; + obj2 = arr.DeepClone(); + Assert.IsFalse(obj2 == arr); + } + + [Test] + public void Test_JsonObjectJsonArrayJsonDocument() + { + var obj = new { Id = 1, Name = "小明" }.ToJson().ToObject() as object; + var obj2 = obj.DeepClone(); + Assert.IsFalse(obj2 == obj); + + var arr = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject() as object; + obj2 = arr.DeepClone(); + Assert.IsFalse(obj2 == arr); + + var doc = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject() as object; + obj2 = doc.DeepClone(); + Assert.IsFalse(obj2 == arr); + } + [Test] public void Test_CloneSimple() { -- Gitee From b039095247ff3c8cb7d1a932f71280e7c27e7634 Mon Sep 17 00:00:00 2001 From: jackletter <1286317554@qq.com> Date: Sun, 4 Jun 2023 21:16:20 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E4=B8=8A=E5=AE=8C?= =?UTF-8?q?=E6=88=90=20v4.0.0=E7=9A=84=E6=94=B9=E9=80=A0=20fixbug:=20DotNe?= =?UTF-8?q?tCommon.Compress=20=E8=A7=A3=E5=8E=8Bzip=E6=9C=AA=E9=87=8A?= =?UTF-8?q?=E6=94=BE=E5=8E=9F=E6=96=87=E4=BB=B6=E5=8D=A0=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++------ docs/README.md | 8 ++------ docs/imgs/image-20221101092958287.png | Bin 27405 -> 26086 bytes src/DotNetCommon.Compress/CompressHelper.cs | 2 +- src/DotNetCommon.Core/DeepCloneHelper.cs | 9 +++++++++ src/DotNetCommon/RoslynMapper.cs | 1 - .../DeepClonePerformanceTest.csproj | 4 ++++ .../DotNetCommon.Test/CompressHelperTests.cs | 18 ++++++++++++------ .../Extensions/ObjectTests_DeepClone.cs | 1 + tests/DotNetCommon.Test/JsonHelperTests.cs | 3 ++- .../DotNetCommon.Test/RegistryHelperTests.cs | 2 ++ .../MapperPerformanceTest.csproj | 3 ++- 12 files changed, 37 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e023be6..d021692 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,7 @@ **注意:** -- **从 3.0 后分离了各个功能包,极力避免`DotNetCommon.Core`对其他包的依赖;** - -- **从 4.0 后使用`SharpCompress`替代`SharpZipLib` 封装解压缩功能;** +- **从 4.0 后移除了对`Newtonsoft.Json`的依赖,这样`DotNetCommon.Core`将不依赖任何三方包;** **整体关系如下图:** @@ -30,9 +28,7 @@ > > 1. DotNetCommon包 具有所有功能,引用了其他的各个功能包; > -> 2. DotNeCommon.Core 是核心包,在 `https://github.com/NimaAra/Easy.Common`基础上扩充而成; -> -> 虽然,极力在避免其他的依赖,但还是无法避免对`Newtonsoft.Json`的引用,因为`System.Text.Json`不支持`jsonpath`。 +> 2. DotNeCommon.Core 是核心包,在 `https://github.com/NimaAra/Easy.Common`基础上扩充而成,从DotNetCommon.Core4.0.0开始移除了对Newtonsoft.Json的依赖,之后将不再依赖任何三方包; > > 3. DotNetCommon.PinYin 是汉字转拼音包,从`https://github.com/toolgood/ToolGood.Words.Pinyin`搬运; > diff --git a/docs/README.md b/docs/README.md index 7289256..859f897 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,9 +16,7 @@ **注意:** -- **从 3.0 后分离了各个功能包,极力避免`DotNetCommon.Core`对其他包的依赖;** - -- **从 4.0 后使用`SharpCompress`替代`SharpZipLib`封装解压缩功能;** +- **从 4.0 后移除了对`Newtonsoft.Json`的依赖,这样`DotNetCommon.Core`将不依赖任何三方包;** **整体关系如下图:** @@ -30,9 +28,7 @@ > > 1. DotNetCommon包 具有所有功能,引用了其他的各个功能包; > -> 2. DotNeCommon.Core 是核心包,在 `https://github.com/NimaAra/Easy.Common`基础上扩充而成; -> -> 虽然,极力在避免其他的依赖,但还是无法避免对`Newtonsoft.Json`的引用,因为`System.Text.Json`不支持`jsonpath`。 +> 2. DotNeCommon.Core 是核心包,在 `https://github.com/NimaAra/Easy.Common`基础上扩充而成,从DotNetCommon.Core4.0.0开始移除了对Newtonsoft.Json的依赖,之后将不再依赖任何三方包; > > 3. DotNetCommon.PinYin 是汉字转拼音包,从`https://github.com/toolgood/ToolGood.Words.Pinyin`搬运; > diff --git a/docs/imgs/image-20221101092958287.png b/docs/imgs/image-20221101092958287.png index b157cdf4fa746a4c28304be5b43ae98c4afbf695..e96c59fd6ffd94335f139525364b9276db443a94 100644 GIT binary patch literal 26086 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 Y*%yCY&R%=5SAk>tr-gCxMdz^p0rGd|AOHXW literal 27405 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 diff --git a/src/DotNetCommon.Compress/CompressHelper.cs b/src/DotNetCommon.Compress/CompressHelper.cs index 05de41e..ac166cf 100644 --- a/src/DotNetCommon.Compress/CompressHelper.cs +++ b/src/DotNetCommon.Compress/CompressHelper.cs @@ -224,7 +224,7 @@ namespace DotNetCommon.Compress } else if (new[] { ".zip", ".7z" }.Any(i => src.EndsWith(i, StringComparison.OrdinalIgnoreCase))) { - var archive = ArchiveFactory.Open(src, GetReaderOptions()); + using var archive = ArchiveFactory.Open(src, GetReaderOptions()); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) diff --git a/src/DotNetCommon.Core/DeepCloneHelper.cs b/src/DotNetCommon.Core/DeepCloneHelper.cs index b739aa1..fb869b2 100644 --- a/src/DotNetCommon.Core/DeepCloneHelper.cs +++ b/src/DotNetCommon.Core/DeepCloneHelper.cs @@ -202,6 +202,15 @@ namespace DotNetCommon wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); return wrapper; } + else if (reflect.Name == "Newtonsoft.Json.Linq.JToken") + { + var tmp = type.Assembly.GetType("Newtonsoft.Json.Linq.JToken"); + var tmp2 = tmp.GetMethod("DeepClone"); + var tmp3 = Expression.Parameter(typeof(object), "obj"); + var act = Expression.Lambda>(Expression.Call(Expression.TypeAs(tmp3, tmp), tmp2), tmp3).Compile(); + wrapper.Method = (obj, dic) => dic.ContainsKey(obj) ? dic.get_Item(obj) : act(obj); + return wrapper; + } #region TODO JsonObject/JsonArray/JsonDocument 原生没有提供clone方法 else if (reflect.Name == "System.Text.Json.Nodes.JsonObject") { diff --git a/src/DotNetCommon/RoslynMapper.cs b/src/DotNetCommon/RoslynMapper.cs index e941e70..12f859a 100644 --- a/src/DotNetCommon/RoslynMapper.cs +++ b/src/DotNetCommon/RoslynMapper.cs @@ -184,7 +184,6 @@ namespace DotNetCommon "System.IO", "System.Collections", "System.Collections.Generic", - //"Newtonsoft.Json", "DotNetCommon.Serialize" }; diff --git a/tests/DeepClonePerformanceTest/DeepClonePerformanceTest.csproj b/tests/DeepClonePerformanceTest/DeepClonePerformanceTest.csproj index 2c6d7bf..99fa14d 100644 --- a/tests/DeepClonePerformanceTest/DeepClonePerformanceTest.csproj +++ b/tests/DeepClonePerformanceTest/DeepClonePerformanceTest.csproj @@ -7,6 +7,10 @@ enable + + + + diff --git a/tests/DotNetCommon.Test/CompressHelperTests.cs b/tests/DotNetCommon.Test/CompressHelperTests.cs index e7edde7..b6c8cb0 100644 --- a/tests/DotNetCommon.Test/CompressHelperTests.cs +++ b/tests/DotNetCommon.Test/CompressHelperTests.cs @@ -43,7 +43,7 @@ namespace DotNetCommon.Test File.AppendAllText(Path.Combine(testfolderPath, "testsubfolder2", "testsubfolder-suba.txt"), "我是testsubfolder-suba.txt"); Directory.CreateDirectory(Path.Combine(testfolderPath, "testemptysubfolder")); } - [Test] + public void UnCompressTests() { var destDirPath = Path.Combine(rootPath, "testcompress_stream.zip"); @@ -85,7 +85,6 @@ namespace DotNetCommon.Test Directory.Delete(destDirPath, true); } - [Test] public void CompressStreamTests() { var destFilePath = Path.Combine(rootPath, "testcompress_stream.zip"); @@ -112,7 +111,6 @@ namespace DotNetCommon.Test File.ReadAllText(Path.Combine(destUnZipFolder, "testfolder-a.txt")).ShouldBe("我是testfolder-a.txt"); } - [Test] public void CompressFilesTests() { var destFilePath = Path.Combine(rootPath, "testcompress_stream.zip"); @@ -133,7 +131,6 @@ namespace DotNetCommon.Test File.ReadAllText(Path.Combine(destUnZipFolder, "testfolder-a.txt")).ShouldBe("我是testfolder-a.txt"); } - [Test] public void CompressZipFolderTests() { var destFilePath = Path.Combine(rootPath, "testcompress_stream.zip"); @@ -152,7 +149,6 @@ namespace DotNetCommon.Test File.ReadAllText(Path.Combine(destUnZipFolder, "testsubfolder2/testsubfolder-suba.txt")).ShouldBe("我是testsubfolder-suba.txt"); } - [Test] public void CompressZipFolderTests2() { var destFilePath = Path.Combine(rootPath, "testcompress_stream.zip"); @@ -167,7 +163,6 @@ namespace DotNetCommon.Test File.ReadAllText(Path.Combine(destUnZipFolder, "testfolder/testfolder-a.txt")).ShouldBe("我是testfolder-a.txt"); } - [Test] public void CompressZipFolderContentTests() { var destFilePath = Path.Combine(rootPath, "testcompress_stream.zip"); @@ -181,5 +176,16 @@ namespace DotNetCommon.Test File.ReadAllText(Path.Combine(destUnZipFolder, "testsubfolder/testsubfolder-suba.txt")).ShouldBe("我是testsubfolder-suba.txt"); File.ReadAllText(Path.Combine(destUnZipFolder, "testfolder-a.txt")).ShouldBe("我是testfolder-a.txt"); } + + [Test] + public void TestFlow() + { + CompressZipFolderContentTests(); + CompressZipFolderTests2(); + CompressZipFolderTests(); + CompressFilesTests(); + CompressStreamTests(); + UnCompressTests(); + } } } diff --git a/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs b/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs index ce0625e..555d0cb 100644 --- a/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs +++ b/tests/DotNetCommon.Test/Extensions/ObjectTests_DeepClone.cs @@ -764,6 +764,7 @@ namespace DotNetCommon.Test.Extensions Assert.IsTrue(list != newList); Assert.IsTrue(newList[0] != list[0]); Assert.IsTrue(newList[1] != list[1]); + newList[0]["Id"].Value().ShouldBe(1); newList[0]["Name"].Value().ShouldBe("小明"); diff --git a/tests/DotNetCommon.Test/JsonHelperTests.cs b/tests/DotNetCommon.Test/JsonHelperTests.cs index b58ce84..4affa03 100644 --- a/tests/DotNetCommon.Test/JsonHelperTests.cs +++ b/tests/DotNetCommon.Test/JsonHelperTests.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; namespace DotNetCommon.Test { @@ -68,7 +70,6 @@ namespace DotNetCommon.Test ""birth"":""1998-02-01"", ""score:"":98.5, ""ref"":null, - ""desc"":undefined }"; var result = JsonHelper.Format(json); diff --git a/tests/DotNetCommon.Test/RegistryHelperTests.cs b/tests/DotNetCommon.Test/RegistryHelperTests.cs index 82b3243..03f4240 100644 --- a/tests/DotNetCommon.Test/RegistryHelperTests.cs +++ b/tests/DotNetCommon.Test/RegistryHelperTests.cs @@ -2,11 +2,13 @@ using NUnit.Framework; using System; using System.Collections.Generic; +using System.Runtime.Versioning; using System.Text; using helper = DotNetCommon.RegistryHelper; namespace DotNetCommon.Test { + [SupportedOSPlatform("Windows")] [TestFixture] public class RegistryHelperTests { diff --git a/tests/MapperPerformanceTest/MapperPerformanceTest.csproj b/tests/MapperPerformanceTest/MapperPerformanceTest.csproj index 0c5144a..23cd0a1 100644 --- a/tests/MapperPerformanceTest/MapperPerformanceTest.csproj +++ b/tests/MapperPerformanceTest/MapperPerformanceTest.csproj @@ -6,7 +6,8 @@ - + + -- Gitee