拥抱System.Text.Json (#27)

* 添加适用于DataSet和DateTime的JSON自定义转换器。

* 添加JsonManager,删除JsonObject并仅使用SocketObject

* 移除Newtonsoft.Json引用
This commit is contained in:
milimoe 2023-06-03 18:57:24 +08:00 committed by GitHub
parent e90d7b075a
commit 23c9ade79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 492 additions and 117 deletions

View File

@ -1,6 +1,5 @@
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
@ -115,6 +114,28 @@ namespace Milimoe.FunGame.Core.Api.Utility
} }
return -1; return -1;
} }
/// <summary>
/// 返回目标对象的Json字符串
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string JsonSerialize<T>(T obj)
{
return Service.JsonManager.GetString(obj);
}
/// <summary>
/// 反序列化Json对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <returns></returns>
public static T? JsonDeserialize<T>(string json)
{
return Service.JsonManager.GetObject<T>(json);
}
} }
#endregion #endregion

View File

@ -24,8 +24,4 @@
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,302 @@
using System.Data;
using System.Text.Json;
using System.Text.Json.Serialization;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Service;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class DataSetConverter : JsonConverter<DataSet>
{
public override DataSet Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
DataSet ds = new();
DataTable dt = new();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
string property = reader.GetString() ?? "";
switch (property)
{
case "TableName":
reader.Read();
string tableName = reader.GetString() ?? "";
dt = new DataTable(tableName);
ds.Tables.Add(dt);
break;
case "Columns":
reader.Read();
ReadColumns(reader, dt);
break;
case "Rows":
reader.Read();
ReadRows(reader, dt);
break;
}
}
}
return ds;
}
public override void Write(Utf8JsonWriter writer, DataSet value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteString("TableName", value.Tables[0].TableName);
writer.WritePropertyName("Columns");
writer.WriteStartArray();
foreach (DataColumn column in value.Tables[0].Columns)
{
writer.WriteStartObject();
writer.WriteString("ColumnName", column.ColumnName);
writer.WriteString("DataType", column.DataType.FullName);
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WritePropertyName("Rows");
writer.WriteStartArray();
foreach (DataRow row in value.Tables[0].Rows)
{
writer.WriteStartArray();
for (int i = 0; i < value.Tables[0].Columns.Count; i++)
{
object? rowValue = row[i];
switch (value.Tables[0].Columns[i].DataType.FullName)
{
case "System.Boolean":
writer.WriteBooleanValue((bool)rowValue);
break;
case "System.Byte":
writer.WriteNumberValue((byte)rowValue);
break;
case "System.Char":
writer.WriteStringValue(value.ToString());
break;
case "System.DateTime":
writer.WriteStringValue(((DateTime)rowValue).ToString(General.GeneralDateTimeFormat));
break;
case "System.Decimal":
writer.WriteNumberValue((decimal)rowValue);
break;
case "System.Double":
writer.WriteNumberValue((double)rowValue);
break;
case "System.Guid":
writer.WriteStringValue(value.ToString());
break;
case "System.Int16":
writer.WriteNumberValue((short)rowValue);
break;
case "System.Int32":
writer.WriteNumberValue((int)rowValue);
break;
case "System.Int64":
writer.WriteNumberValue((long)rowValue);
break;
case "System.SByte":
writer.WriteNumberValue((sbyte)rowValue);
break;
case "System.Single":
writer.WriteNumberValue((float)rowValue);
break;
case "System.String":
writer.WriteStringValue((string)rowValue);
break;
case "System.UInt16":
writer.WriteNumberValue((ushort)rowValue);
break;
case "System.UInt32":
writer.WriteNumberValue((uint)rowValue);
break;
case "System.UInt64":
writer.WriteNumberValue((ulong)rowValue);
break;
}
}
writer.WriteEndArray();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
#pragma warning disable CA1822 // 不需要设为静态
private void ReadColumns(Utf8JsonReader reader, DataTable dataTable)
#pragma warning restore CA1822 // 不需要设为静态
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
if (reader.TokenType == JsonTokenType.StartObject)
{
DataColumn column = new();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = reader.GetString() ?? "";
switch (propertyName)
{
case "ColumnName":
reader.Read();
column.ColumnName = reader.GetString();
break;
case "DataType":
reader.Read();
Type dataType = Type.GetType(reader.GetString() ?? "") ?? typeof(object);
column.DataType = dataType;
break;
}
}
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
}
dataTable.Columns.Add(column);
}
}
}
private void ReadRows(Utf8JsonReader reader, DataTable dataTable)
{
object[] values = new object[dataTable.Columns.Count];
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
if (reader.TokenType == JsonTokenType.StartArray)
{
int index = 0;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
switch (dataTable.Columns[index].DataType.ToString())
{
case "System.Boolean":
values[index] = reader.GetBoolean();
break;
case "System.Byte":
values[index] = reader.GetByte();
break;
case "System.Char":
values[index] = (reader.GetString() ?? "")[0];
break;
case "System.DateTime":
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime result))
{
values[index] = result;
}
values[index] = DateTime.MinValue;
break;
case "System.Decimal":
values[index] = reader.GetDecimal();
break;
case "System.Double":
values[index] = reader.GetDouble();
break;
case "System.Guid":
values[index] = Guid.Parse(reader.GetString() ?? Guid.Empty.ToString());
break;
case "System.Int16":
values[index] = reader.GetInt16();
break;
case "System.Int32":
values[index] = reader.GetInt32();
break;
case "System.Int64":
values[index] = reader.GetInt64();
break;
case "System.SByte":
values[index] = reader.GetSByte();
break;
case "System.Single":
values[index] = reader.GetSingle();
break;
case "System.String":
values[index] = reader.GetString() ?? "";
break;
case "System.UInt16":
values[index] = reader.GetUInt16();
break;
case "System.UInt32":
values[index] = reader.GetUInt32();
break;
case "System.UInt64":
values[index] = reader.GetUInt64();
break;
}
index++;
}
dataTable.Rows.Add(values);
}
}
}
}
}

View File

@ -0,0 +1,31 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
string date = reader.GetString() ?? "";
if (DateTime.TryParseExact(date, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime result))
{
return result;
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(General.GeneralDateTimeFormat));
}
}
}

View File

@ -66,7 +66,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Network
{ {
if (Instance != null) if (Instance != null)
{ {
if (SocketManager.Send(Instance, type, Token, objs) == SocketResult.Success) if (SocketManager.Send(Instance, new(type, Token, objs)) == SocketResult.Success)
{ {
return SocketResult.Success; return SocketResult.Success;
} }

View File

@ -1,67 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.Network
{
[Serializable]
public struct JsonObject
{
public SocketMessageType MessageType { get; } = SocketMessageType.Unknown;
public Guid Token { get; }
public object[] Parameters { get; }
public string JsonString { get; }
private JArray? JArray;
public JsonObject(SocketMessageType MessageType, Guid Token, params object[] Parameters)
{
this.MessageType = MessageType;
this.Token = Token;
this.Parameters = Parameters;
this.JsonString = JsonConvert.SerializeObject(this, Formatting.Indented);
}
public T? GetObject<T>(int i)
{
if (i >= Parameters.Length) throw new IndexOutOfArrayLengthException();
JArray ??= JArray.FromObject(Parameters);
return JArray[i].ToObject<T>();
}
public static string GetString(SocketMessageType MessageType, Guid Token, params object[] Parameters)
{
return new JsonObject(MessageType, Token, Parameters).JsonString;
}
public static JsonObject GetObject(string JsonString)
{
return JsonConvert.DeserializeObject<JsonObject>(JsonString);
}
public static JsonObject[] GetObjects(string JsonString)
{
List<JsonObject> jsons = new();
JsonSerializer serializer = new();
JsonTextReader reader = new(new StringReader(JsonString))
{
SupportMultipleContent = true
};
while (true)
{
if (!reader.Read())
{
break;
}
JsonObject json = serializer.Deserialize<JsonObject>(reader);
jsons.Add(json);
}
return jsons.ToArray();
}
}
}

View File

@ -45,7 +45,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Network
{ {
if (Instance != null) if (Instance != null)
{ {
if (SocketManager.Send(type, Token, objs) == SocketResult.Success) if (SocketManager.Send(new(type, Token, objs)) == SocketResult.Success)
{ {
return SocketResult.Success; return SocketResult.Success;
} }

View File

@ -1,4 +1,6 @@
using Milimoe.FunGame.Core.Library.Constant; using System.Text.Json.Serialization;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Service;
namespace Milimoe.FunGame.Core.Library.Common.Network namespace Milimoe.FunGame.Core.Library.Common.Network
{ {
@ -8,34 +10,47 @@ namespace Milimoe.FunGame.Core.Library.Common.Network
public SocketMessageType SocketType { get; } = SocketMessageType.Unknown; public SocketMessageType SocketType { get; } = SocketMessageType.Unknown;
public Guid Token { get; } = Guid.Empty; public Guid Token { get; } = Guid.Empty;
public object[] Parameters { get; } = Array.Empty<object>(); public object[] Parameters { get; } = Array.Empty<object>();
public int Length { get; } = 0; public int Length => Parameters.Length;
private JsonObject Json { get; }
public SocketObject(JsonObject json) /// <summary>
/// 从参数列表中获取指定索引的参数的Json字符串
/// -- 此方法仅返回Json字符串对象类型请使用反序列化方法GetParam<T>() --
/// -- 当然也可以自己反序列化 --
/// </summary>
/// <param name="index">索引</param>
/// <returns></returns>
/// <exception cref="IndexOutOfArrayLengthException">索引超过数组上限</exception>
public object? this[int index]
{ {
Json = json; get
SocketType = Json.MessageType; {
Token = Json.Token; if (index >= Parameters.Length) throw new IndexOutOfArrayLengthException();
Parameters = Json.Parameters; object? obj = Parameters[index];
Length = Parameters.Length; return JsonManager.GetObject(obj.ToString() ?? "");
}
} }
public SocketObject() [JsonConstructor]
public SocketObject(SocketMessageType SocketType, Guid Token, params object[] Parameters)
{ {
Json = new JsonObject(SocketMessageType.Unknown, Guid.Empty, Array.Empty<object>()); this.SocketType = SocketType;
SocketType = Json.MessageType; this.Token = Token;
Token = Json.Token; if (Parameters != null && Parameters.Length > 0) this.Parameters = Parameters;
Parameters = Json.Parameters;
Length = Parameters.Length;
} }
/// <summary> /// <summary>
/// 从参数列表中获取指定类型的参数 /// 从参数列表中获取指定类型和索引的参数
/// </summary> /// </summary>
/// <typeparam name="T">类型</typeparam> /// <typeparam name="T">类型</typeparam>
/// <param name="index">索引</param> /// <param name="index">索引</param>
/// <returns>类型的参数</returns> /// <returns>类型的参数</returns>
/// <exception cref="IndexOutOfArrayLengthException">索引超过数组上限</exception> /// <exception cref="IndexOutOfArrayLengthException">索引超过数组上限</exception>
public T? GetParam<T>(int index) => Json.GetObject<T>(index); public T? GetParam<T>(int index)
{
if (index >= Parameters.Length) throw new IndexOutOfArrayLengthException();
object obj = Parameters[index];
T? result = JsonManager.GetObject<T>(obj.ToString() ?? "");
return result;
}
} }
} }

View File

@ -8,8 +8,9 @@ namespace Milimoe.FunGame.Core.Library.Constant
// Static Variable // Static Variable
public static Empty EntityInstance { get; } = new(); public static Empty EntityInstance { get; } = new();
public static User UnknownUserInstance { get; } = new(); public static User UnknownUserInstance { get; } = new();
public static Room HallInstance { get; } = Api.Utility.Factory.GetHall(); public static Room HallInstance => Api.Utility.Factory.GetHall();
public static Encoding DefaultEncoding { get; } = Encoding.Unicode; public static Encoding DefaultEncoding => Encoding.Unicode;
public static string GeneralDateTimeFormat => "yyyy-MM-dd HH:mm:ss.fff";
// Const // Const
public const int MaxRetryTimes = 20; public const int MaxRetryTimes = 20;

88
Service/JsonManager.cs Normal file
View File

@ -0,0 +1,88 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Milimoe.FunGame.Core.Library.Common.JsonConverter;
namespace Milimoe.FunGame.Core.Service
{
public class JsonManager
{
private static bool _IsFirst = true;
private readonly static JsonSerializerOptions _GeneralOptions = new()
{
WriteIndented = true,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
/// <summary>
/// 默认的JsonSerializerOptions
/// </summary>
internal static JsonSerializerOptions GeneralOptions
{
get
{
if (_IsFirst)
{
// 首次使用时,向其添加自定义转换器
_IsFirst = false;
AddConverter(new DataSetConverter());
AddConverter(new DateTimeConverter());
}
return _GeneralOptions;
}
}
/// <summary>
/// 注册一个自定义转换器
/// </summary>
/// <param name="converter"></param>
internal static void AddConverter(JsonConverter converter)
{
_GeneralOptions.Converters.Add(converter);
}
/// <summary>
/// 获取Json字符串
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string GetString<T>(T obj)
{
return JsonSerializer.Serialize(obj, GeneralOptions);
}
/// <summary>
/// 反序列化Json对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <returns></returns>
public static T? GetObject<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, GeneralOptions);
}
/// <summary>
/// 反序列化Json对象此方法可能无法返回正确的类型请注意辨别
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public static object? GetObject(string json)
{
return JsonSerializer.Deserialize<object>(json, GeneralOptions);
}
/// <summary>
/// 反序列化多个Json对象
/// 注意必须是相同的Json对象才可以使用此方法解析
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <returns></returns>
public static List<T> GetObjects<T>(string json)
{
json = "[" + json.Replace("}{", "},{") + "]"; // 将Json字符串转换为数组
return JsonSerializer.Deserialize<List<T>>(json, GeneralOptions) ?? new List<T>();
}
}
}

View File

@ -116,14 +116,13 @@ namespace Milimoe.FunGame.Core.Service
/// 用于服务器端向客户端Socket发送信息 /// 用于服务器端向客户端Socket发送信息
/// </summary> /// </summary>
/// <param name="ClientSocket">客户端Socket</param> /// <param name="ClientSocket">客户端Socket</param>
/// <param name="type">通信类型</param> /// <param name="SocketObject">Socket信息容器</param>
/// <param name="objs">参数</param>
/// <returns>通信结果</returns> /// <returns>通信结果</returns>
internal static SocketResult Send(Socket ClientSocket, SocketMessageType type, Guid token, params object[] objs) internal static SocketResult Send(Socket ClientSocket, Library.Common.Network.SocketObject SocketObject)
{ {
if (ClientSocket != null && objs != null && objs.Length > 0) if (ClientSocket != null)
{ {
if (ClientSocket.Send(General.DefaultEncoding.GetBytes(Library.Common.Network.JsonObject.GetString(type, token, objs))) > 0) if (ClientSocket.Send(General.DefaultEncoding.GetBytes(JsonManager.GetString(SocketObject))) > 0)
{ {
return SocketResult.Success; return SocketResult.Success;
} }
@ -135,18 +134,13 @@ namespace Milimoe.FunGame.Core.Service
/// <summary> /// <summary>
/// 用于客户端向服务器Socket发送信息 /// 用于客户端向服务器Socket发送信息
/// </summary> /// </summary>
/// <param name="type">通信类型</param> /// <param name="SocketObject">Socket信息容器</param>
/// <param name="objs">参数</param>
/// <returns>通信结果</returns> /// <returns>通信结果</returns>
internal static SocketResult Send(SocketMessageType type, Guid token, params object[] objs) internal static SocketResult Send(Library.Common.Network.SocketObject SocketObject)
{ {
if (objs is null || objs.Length <= 0)
{
objs = new object[] { "" };
}
if (Socket != null) if (Socket != null)
{ {
if (Socket.Send(General.DefaultEncoding.GetBytes(Library.Common.Network.JsonObject.GetString(type, token, objs))) > 0) if (Socket.Send(General.DefaultEncoding.GetBytes(JsonManager.GetString(SocketObject))) > 0)
{ {
return SocketResult.Success; return SocketResult.Success;
} }
@ -170,8 +164,7 @@ namespace Milimoe.FunGame.Core.Service
if (length > 0) if (length > 0)
{ {
string msg = General.DefaultEncoding.GetString(buffer, 0, length); string msg = General.DefaultEncoding.GetString(buffer, 0, length);
Library.Common.Network.JsonObject json = Library.Common.Network.JsonObject.GetObject(msg); result = JsonManager.GetObject<Library.Common.Network.SocketObject>(msg);
result = new Library.Common.Network.SocketObject(json);
// 客户端接收消息广播ScoketObject到每个UIModel // 客户端接收消息广播ScoketObject到每个UIModel
OnSocketReceive(result); OnSocketReceive(result);
return result; return result;
@ -196,8 +189,7 @@ namespace Milimoe.FunGame.Core.Service
if (length > 0) if (length > 0)
{ {
string msg = General.DefaultEncoding.GetString(buffer, 0, length); string msg = General.DefaultEncoding.GetString(buffer, 0, length);
Library.Common.Network.JsonObject json = Library.Common.Network.JsonObject.GetObject(msg); result = JsonManager.GetObject<Library.Common.Network.SocketObject>(msg);
result = new Library.Common.Network.SocketObject(json);
return result; return result;
} }
} }
@ -219,11 +211,9 @@ namespace Milimoe.FunGame.Core.Service
if (length > 0) if (length > 0)
{ {
string msg = General.DefaultEncoding.GetString(buffer, 0, length); string msg = General.DefaultEncoding.GetString(buffer, 0, length);
Library.Common.Network.JsonObject[] jsons = Library.Common.Network.JsonObject.GetObjects(msg); foreach (Library.Common.Network.SocketObject obj in JsonManager.GetObjects<Library.Common.Network.SocketObject>(msg))
foreach (Library.Common.Network.JsonObject json in jsons)
{ {
// 客户端接收消息广播ScoketObject到每个UIModel // 客户端接收消息广播ScoketObject到每个UIModel
Library.Common.Network.SocketObject obj = new(json);
result.Add(obj); result.Add(obj);
OnSocketReceive(obj); OnSocketReceive(obj);
} }
@ -248,11 +238,9 @@ namespace Milimoe.FunGame.Core.Service
if (length > 0) if (length > 0)
{ {
string msg = General.DefaultEncoding.GetString(buffer, 0, length); string msg = General.DefaultEncoding.GetString(buffer, 0, length);
Library.Common.Network.JsonObject[] jsons = Library.Common.Network.JsonObject.GetObjects(msg); foreach (Library.Common.Network.SocketObject obj in JsonManager.GetObjects<Library.Common.Network.SocketObject>(msg))
foreach (Library.Common.Network.JsonObject json in jsons)
{ {
Library.Common.Network.SocketObject so = new(json); result.Add(obj);
result.Add(so);
} }
} }
} }