mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-09-18 09:44:43 +08:00
refactor(tenpayv2): 优化反射性能
This commit is contained in:
@@ -1,54 +1,44 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Converters.Common;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Newtonsoft.Json.Converters
|
||||
namespace Newtonsoft.Json.Converters.Internal
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities;
|
||||
|
||||
internal static class FlattenNArrayObjectConverterBase
|
||||
internal abstract class FlattenNArrayObjectConverterBase<TModelContainer, TFlattenElement> : JsonConverter
|
||||
where TModelContainer : class, new()
|
||||
where TFlattenElement: class, new()
|
||||
{
|
||||
public const string PROPERTY_WILDCARD_NARRAY_ELEMENT = "$n";
|
||||
public const string PROPERTY_NAME_NARRAY = "#n";
|
||||
}
|
||||
public const string FLATTEN_PROPERTY_JSON_NAME = "#n";
|
||||
|
||||
internal abstract partial class FlattenNArrayObjectConverterBase<T> : JsonConverter
|
||||
where T : class, new()
|
||||
{
|
||||
private sealed class InnerTypedJsonPropertyInfo
|
||||
private static bool TryMatchNArrayIndex(string key, out int index)
|
||||
{
|
||||
public string PropertyName { get; }
|
||||
|
||||
public PropertyInfo PropertyInfo { get; }
|
||||
|
||||
public Type PropertyType { get { return PropertyInfo.PropertyType; } }
|
||||
|
||||
public bool PropertyIsNArray { get; }
|
||||
|
||||
public JsonConverter? JsonConverterOnRead { get; }
|
||||
|
||||
public JsonConverter? JsonConverterOnWrite { get; }
|
||||
|
||||
public InnerTypedJsonPropertyInfo(string propertyName, PropertyInfo propertyInfo, bool propertyIsNArray, JsonConverter? jsonReadConverter, JsonConverter? jsonWriteConverter)
|
||||
Regex regex = new Regex(@"_(\d+)$", RegexOptions.Compiled);
|
||||
if (regex.IsMatch(key))
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
PropertyInfo = propertyInfo;
|
||||
PropertyIsNArray = propertyIsNArray;
|
||||
JsonConverterOnRead = jsonReadConverter;
|
||||
JsonConverterOnWrite = jsonWriteConverter;
|
||||
string str = regex.Match(key).Groups[1].Value;
|
||||
index = int.Parse(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
private const string PROPERTY_WILDCARD_NARRAY_ELEMENT = FlattenNArrayObjectConverterBase.PROPERTY_WILDCARD_NARRAY_ELEMENT;
|
||||
private const string PROPERTY_NAME_NARRAY = FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY;
|
||||
private static JsonObjectContract ResolveJsonType(Type type, JsonSerializer serializer)
|
||||
{
|
||||
IContractResolver resolver = serializer.ContractResolver ?? JsonSerializer.CreateDefault().ContractResolver;
|
||||
return (JsonObjectContract)resolver.ResolveContract(type);
|
||||
}
|
||||
|
||||
private static readonly Hashtable _mappedTypeJsonProperties = new Hashtable();
|
||||
/// <summary>
|
||||
/// 获取 `<see cref="TModelContainer"/>` 类下需要扁平展开的属性,该属性需是一个数组类型、其中每个元素是一个 `<see cref="TFlattenElement"/>`。
|
||||
/// </summary>
|
||||
protected abstract PropertyInfo FlattenProperty { get; }
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
@@ -62,7 +52,11 @@ namespace Newtonsoft.Json.Converters
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType.IsClass && !objectType.IsAbstract && !objectType.IsInterface;
|
||||
if (!FlattenProperty.PropertyType.IsArray)
|
||||
throw new NotSupportedException();
|
||||
|
||||
return objectType.IsClass && !objectType.IsAbstract && !objectType.IsInterface
|
||||
&& typeof(TModelContainer).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
@@ -73,72 +67,92 @@ namespace Newtonsoft.Json.Converters
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.StartObject)
|
||||
{
|
||||
TModelContainer outputObj = existingValue as TModelContainer ?? ReflectionHelper.CreateInstance<TModelContainer>();
|
||||
|
||||
// 读取为 JObject
|
||||
JObject jObject = JObject.Load(reader);
|
||||
T tObject = existingValue as T ?? new T();
|
||||
if (!jObject.Properties().Any())
|
||||
return outputObj;
|
||||
|
||||
JsonObjectContract jsonContract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(typeof(T));
|
||||
foreach (JProperty jProperty in jObject.Properties())
|
||||
// 取得下标最大值(-1 表示无需扁平展开)
|
||||
int maxIndex = jObject.Properties().Select(e => TryMatchNArrayIndex(e.Name!, out int index) ? index : -1).Max();
|
||||
|
||||
// 解析 JSON 结构
|
||||
JsonObjectContract jsonContract = ResolveJsonType(objectType, serializer);
|
||||
|
||||
// 遍历 JObject 并反序列化
|
||||
foreach (JProperty jProp in jObject.Properties())
|
||||
{
|
||||
string propertyName = jProperty.Name!;
|
||||
string propertyName = jProp.Name!;
|
||||
|
||||
if (TryMatchNArrayIndex(propertyName, out int index))
|
||||
if (maxIndex != -1 && TryMatchNArrayIndex(propertyName, out int index))
|
||||
{
|
||||
InnerTypedJsonPropertyInfo[] typedJsonProperties = GetTypedJsonProperties(objectType);
|
||||
if (typedJsonProperties.Count(p => p.PropertyIsNArray) != 1)
|
||||
throw new JsonSerializationException("The count of `$n` properties must be only one.");
|
||||
|
||||
foreach (InnerTypedJsonPropertyInfo propertyJsonInfo in typedJsonProperties.Where(e => e.PropertyIsNArray))
|
||||
// 反序列化需扁平展开的字段
|
||||
JsonObjectContract flattenJsonContract = ResolveJsonType(FlattenProperty.PropertyType.GetElementType()!, serializer);
|
||||
JsonProperty? jsonProperty = flattenJsonContract.Properties.FirstOrDefault(e => propertyName == e.PropertyName?.Replace("$n", index.ToString()));
|
||||
if (jsonProperty is null)
|
||||
{
|
||||
Array? propertyValue = propertyJsonInfo.PropertyInfo.GetValue(tObject) as Array;
|
||||
ReflectionHelper.CreateOrExpandArray(ref propertyValue, propertyJsonInfo.PropertyType.GetElementType()!, index + 1);
|
||||
ReflectionHelper.CreateOrExpandArrayElement(propertyValue!, index, (object element) =>
|
||||
if (serializer.MissingMemberHandling == MissingMemberHandling.Error)
|
||||
throw new JsonException($"Could not find member '{propertyName}' on object of type '{objectType.FullName}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Array? array = ReflectionHelper.GetPropertyValue<Array>(outputObj, FlattenProperty);
|
||||
if (array is null)
|
||||
{
|
||||
InnerTypedJsonPropertyInfo? elementPropertyJsonInfo = GetTypedJsonProperties(element.GetType())
|
||||
.SingleOrDefault(p => string.Equals(p.PropertyName.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, index.ToString()), jProperty.Name));
|
||||
if (elementPropertyJsonInfo is not null)
|
||||
{
|
||||
JsonSerializer serializerCopy = JsonSerializer.Create(serializer.ExtractSerializerSettings());
|
||||
serializerCopy.Converters.Remove(this);
|
||||
array = new TFlattenElement[maxIndex + 1];
|
||||
ReflectionHelper.SetPropertyValue(outputObj, FlattenProperty, array);
|
||||
}
|
||||
|
||||
if (elementPropertyJsonInfo.JsonConverterOnRead is not null)
|
||||
serializerCopy.Converters.Add(elementPropertyJsonInfo.JsonConverterOnRead);
|
||||
TFlattenElement? element = ReflectionHelper.GetElementValue<TFlattenElement>(array, index);
|
||||
if (element is null)
|
||||
{
|
||||
element = ReflectionHelper.CreateInstance<TFlattenElement>();
|
||||
ReflectionHelper.SetElementValue(array, index, element);
|
||||
}
|
||||
|
||||
object? elementPropertyValue = jProperty.Value?.ToObject(elementPropertyJsonInfo.PropertyType, serializerCopy);
|
||||
elementPropertyJsonInfo.PropertyInfo.SetValue(element, elementPropertyValue);
|
||||
}
|
||||
if (jsonProperty.ShouldDeserialize is not null && !jsonProperty.ShouldDeserialize(array))
|
||||
continue;
|
||||
if (!jsonProperty.Writable)
|
||||
continue;
|
||||
|
||||
return element;
|
||||
});
|
||||
|
||||
propertyJsonInfo.PropertyInfo.SetValue(tObject, propertyValue);
|
||||
object? elementPropertyValue = jProp.Value?.ToObject(jsonProperty.PropertyType, CopyJsonSerializer(serializer, jsonProperty.Converter));
|
||||
jsonProperty.ValueProvider!.SetValue(element, elementPropertyValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonProperty? jsonContractProperty = jsonContract.Properties[propertyName];
|
||||
if (jsonContractProperty is not null && jsonContractProperty.Writable)
|
||||
JsonProperty? jsonProperty = jsonContract.Properties.GetClosestMatchProperty(propertyName);
|
||||
// 反序列化普通字段
|
||||
if (jsonProperty is not null)
|
||||
{
|
||||
JsonSerializer serializerCopy = JsonSerializer.Create(serializer.ExtractSerializerSettings());
|
||||
serializerCopy.Converters.Remove(this);
|
||||
if (jsonProperty.Ignored && jsonProperty.PropertyName is null)
|
||||
continue;
|
||||
if (jsonProperty.ShouldDeserialize is not null && !jsonProperty.ShouldDeserialize(outputObj))
|
||||
continue;
|
||||
if (!jsonProperty.Writable)
|
||||
continue;
|
||||
|
||||
if (jsonContractProperty.Converter is not null)
|
||||
serializerCopy.Converters.Add(jsonContractProperty.Converter);
|
||||
|
||||
object? propertyValue = jProperty.Value.ToObject(jsonContractProperty.PropertyType, serializerCopy);
|
||||
jsonContractProperty.ValueProvider!.SetValue(tObject, propertyValue);
|
||||
object? propertyValue = jProp.Value?.ToObject(jsonProperty.PropertyType, CopyJsonSerializer(serializer, jsonProperty.Converter));
|
||||
jsonProperty.ValueProvider!.SetValue(outputObj, propertyValue);
|
||||
}
|
||||
// 反序列化扩展字段
|
||||
else if (jsonContract.ExtensionDataSetter is not null)
|
||||
{
|
||||
throw new NotSupportedException($"'{GetType().FullName}' does not support JsonExtensionData.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tObject;
|
||||
return outputObj;
|
||||
}
|
||||
|
||||
throw new JsonException($"Unexpected JSON token type '{reader.TokenType}' when reading.");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
public override void WriteJson(JsonWriter writer, object? inputObj, JsonSerializer serializer)
|
||||
{
|
||||
if (value is null)
|
||||
if (inputObj is null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
return;
|
||||
@@ -146,16 +160,29 @@ namespace Newtonsoft.Json.Converters
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
JsonObjectContract jsonContract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
|
||||
foreach (JsonProperty jsonContractProperty in jsonContract.Properties.OrderBy(e => e.Order))
|
||||
// 解析 JSON 结构
|
||||
JsonObjectContract jsonContract = ResolveJsonType(inputObj.GetType(), serializer);
|
||||
|
||||
// 序列化普通字段
|
||||
foreach (JsonProperty jsonProperty in jsonContract.Properties.OrderBy(e => e.Order))
|
||||
{
|
||||
if (jsonContractProperty.Ignored)
|
||||
if (jsonProperty.Ignored && jsonProperty.PropertyName is null)
|
||||
continue;
|
||||
if (!(jsonContractProperty.ShouldSerialize is null || jsonContractProperty.ShouldSerialize(value)))
|
||||
if (jsonProperty.ShouldSerialize is not null && !jsonProperty.ShouldSerialize(inputObj))
|
||||
continue;
|
||||
if (!jsonProperty.Readable)
|
||||
continue;
|
||||
|
||||
string propertyName = jsonContractProperty.PropertyName!;
|
||||
object? propertyValue = jsonContractProperty.ValueProvider?.GetValue(value);
|
||||
string propertyName = jsonProperty.PropertyName ?? jsonProperty.UnderlyingName!;
|
||||
if (FLATTEN_PROPERTY_JSON_NAME.Equals(propertyName))
|
||||
{
|
||||
if (jsonProperty.PropertyType != FlattenProperty.PropertyType)
|
||||
throw new JsonException("Ambiguous flatten property found.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
object? propertyValue = jsonProperty.ValueProvider?.GetValue(inputObj);
|
||||
if (propertyValue is null)
|
||||
{
|
||||
if (serializer.NullValueHandling == NullValueHandling.Include)
|
||||
@@ -163,100 +190,71 @@ namespace Newtonsoft.Json.Converters
|
||||
writer.WritePropertyName(propertyName);
|
||||
writer.WriteNull();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PROPERTY_NAME_NARRAY.Equals(propertyName))
|
||||
{
|
||||
writer.WritePropertyName(propertyName);
|
||||
|
||||
if (jsonContractProperty.Converter is not null && jsonContractProperty.Converter.CanWrite)
|
||||
{
|
||||
jsonContractProperty.Converter.WriteJson(writer, propertyValue, serializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
serializer.Serialize(writer, propertyValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JArray jArray = JArray.FromObject(propertyValue);
|
||||
for (int i = 0, len = jArray.Count; i < len; i++)
|
||||
{
|
||||
JToken? jSubToken = jArray[i];
|
||||
if (jSubToken is null)
|
||||
continue;
|
||||
writer.WritePropertyName(propertyName);
|
||||
|
||||
foreach (JProperty jSubProperty in jSubToken)
|
||||
if (jsonProperty.Converter is not null && jsonProperty.Converter.CanWrite)
|
||||
jsonProperty.Converter.WriteJson(writer, propertyValue, serializer);
|
||||
else
|
||||
serializer.Serialize(writer, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化扩展字段
|
||||
if (jsonContract.ExtensionDataGetter is not null)
|
||||
{
|
||||
throw new NotSupportedException($"'{GetType().FullName}' does not support JsonExtensionData.");
|
||||
}
|
||||
|
||||
// 序列化需扁平展开的字段
|
||||
Array? flattenArray = ReflectionHelper.GetPropertyValue<Array>(inputObj, FlattenProperty);
|
||||
if (flattenArray is not null)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (JToken jToken in JArray.FromObject(flattenArray))
|
||||
{
|
||||
if (jToken.Type != JTokenType.Null)
|
||||
{
|
||||
foreach (JProperty jProp in jToken)
|
||||
{
|
||||
writer.WritePropertyName(jSubProperty.Name.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, i.ToString()));
|
||||
serializer.Serialize(writer, jSubProperty.Value);
|
||||
string propertyName = jProp.Name.Replace("$n", index.ToString());
|
||||
if (jProp.Value is null || jProp.Value.Type == JTokenType.Null)
|
||||
{
|
||||
if (serializer.NullValueHandling == NullValueHandling.Include)
|
||||
{
|
||||
writer.WritePropertyName(propertyName);
|
||||
writer.WriteNull();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WritePropertyName(propertyName);
|
||||
serializer.Serialize(writer, jProp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static InnerTypedJsonPropertyInfo[] GetTypedJsonProperties(Type type)
|
||||
private JsonSerializer CopyJsonSerializer(JsonSerializer serializer, params JsonConverter?[] customConverters)
|
||||
{
|
||||
if (type is null) throw new ArgumentNullException(nameof(type));
|
||||
JsonSerializer serializerCopy = JsonSerializer.Create(serializer.ExtractSerializerSettings());
|
||||
serializerCopy.Converters.Remove(this);
|
||||
|
||||
string mappedKey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
|
||||
InnerTypedJsonPropertyInfo[]? mappedValue = (InnerTypedJsonPropertyInfo[]?)_mappedTypeJsonProperties[mappedKey];
|
||||
|
||||
if (mappedValue is null)
|
||||
foreach (JsonConverter? converter in customConverters)
|
||||
{
|
||||
mappedValue = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p =>
|
||||
{
|
||||
if (p.CanWrite || p.GetCustomAttributes<JsonPropertyAttribute>(inherit: true).Any())
|
||||
{
|
||||
return !p.GetCustomAttributes<JsonIgnoreAttribute>(inherit: false).Any();
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.Select(p =>
|
||||
{
|
||||
string name = p.GetCustomAttribute<JsonPropertyAttribute>(inherit: true)?.PropertyName ?? p.Name;
|
||||
IEnumerable<JsonConverter> converters = p.GetCustomAttributes<JsonConverterAttribute>(inherit: true)
|
||||
.OrderByDescending(attr => attr.IsDefaultAttribute())
|
||||
.Select(attr => (JsonConverter)Activator.CreateInstance(attr.ConverterType, attr.ConverterParameters)!);
|
||||
JsonConverter? readConverter = converters.FirstOrDefault(c => c.CanRead);
|
||||
JsonConverter? writeConverter = converters.FirstOrDefault(c => c.CanWrite);
|
||||
return new InnerTypedJsonPropertyInfo
|
||||
(
|
||||
propertyName: name,
|
||||
propertyInfo: p,
|
||||
propertyIsNArray: PROPERTY_NAME_NARRAY.Equals(name) && p.PropertyType.IsArray && p.PropertyType.GetElementType()!.IsClass,
|
||||
jsonReadConverter: readConverter,
|
||||
jsonWriteConverter: writeConverter
|
||||
);
|
||||
})
|
||||
.OrderBy(e => e.PropertyInfo.GetCustomAttribute<JsonPropertyAttribute>(inherit: true)?.Order)
|
||||
.ToArray();
|
||||
_mappedTypeJsonProperties[mappedKey] = mappedValue;
|
||||
if (converter is not null)
|
||||
serializerCopy.Converters.Add(converter);
|
||||
}
|
||||
|
||||
return mappedValue;
|
||||
}
|
||||
|
||||
private static bool TryMatchNArrayIndex(string key, out int index)
|
||||
{
|
||||
Regex regex = new Regex(@"(_)(\d+)$", RegexOptions.Compiled);
|
||||
if (regex.IsMatch(key))
|
||||
{
|
||||
string str = regex.Match(key).Groups[2].Value;
|
||||
index = int.Parse(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
return false;
|
||||
return serializerCopy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,265 +3,24 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace System.Text.Json.Converters
|
||||
namespace System.Text.Json.Serialization.Internal
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities;
|
||||
|
||||
internal static class FlattenNArrayObjectConverterBase
|
||||
internal abstract partial class FlattenNArrayObjectConverterBase<TModelContainer, TFlattenElement> : JsonConverter<TModelContainer?>
|
||||
where TModelContainer : class, new()
|
||||
where TFlattenElement : class, new()
|
||||
{
|
||||
public const string PROPERTY_WILDCARD_NARRAY_ELEMENT = Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_WILDCARD_NARRAY_ELEMENT;
|
||||
public const string PROPERTY_NAME_NARRAY = Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY;
|
||||
}
|
||||
|
||||
internal class FlattenNArrayObjectConverterBase<T> : JsonConverter<T?>
|
||||
where T : class, new()
|
||||
{
|
||||
private sealed class InnerTypedJsonPropertyInfo
|
||||
{
|
||||
public string PropertyName { get; }
|
||||
|
||||
public PropertyInfo PropertyInfo { get; }
|
||||
|
||||
public Type PropertyType { get { return PropertyInfo.PropertyType; } }
|
||||
|
||||
public bool PropertyIsNArray { get; }
|
||||
|
||||
public JsonConverter? JsonConverter { get; }
|
||||
|
||||
public InnerTypedJsonPropertyInfo(string propertyName, PropertyInfo propertyInfo, bool propertyIsNArray, JsonConverter? jsonConverter)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
PropertyInfo = propertyInfo;
|
||||
PropertyIsNArray = propertyIsNArray;
|
||||
JsonConverter = jsonConverter;
|
||||
}
|
||||
}
|
||||
|
||||
private const string PROPERTY_WILDCARD_NARRAY_ELEMENT = FlattenNArrayObjectConverterBase.PROPERTY_WILDCARD_NARRAY_ELEMENT;
|
||||
private const string PROPERTY_NAME_NARRAY = FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY;
|
||||
|
||||
private static readonly Hashtable _mappedTypeJsonProperties = new Hashtable();
|
||||
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return typeToConvert.IsClass && !typeToConvert.IsAbstract && !typeToConvert.IsInterface;
|
||||
}
|
||||
|
||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.StartObject)
|
||||
{
|
||||
InnerTypedJsonPropertyInfo[] typedJsonProperties = GetTypedJsonProperties(typeToConvert);
|
||||
//if (typedJsonProperties.Count(p => p.PropertyIsNArray) != 1)
|
||||
// throw new JsonException("The count of `$n` properties must be only one.");
|
||||
|
||||
JsonElement jElement = JsonDocument.ParseValue(ref reader).RootElement.Clone();
|
||||
T tObject = new T();
|
||||
|
||||
foreach (JsonProperty jProperty in jElement.EnumerateObject())
|
||||
{
|
||||
InnerTypedJsonPropertyInfo? typedJsonPropertyInfo = typedJsonProperties.SingleOrDefault(e => e.PropertyName == jProperty.Name);
|
||||
if (typedJsonPropertyInfo is not null)
|
||||
{
|
||||
JsonSerializerOptions tmpOptions = GetClonedJsonSerializerOptions(options, typedJsonPropertyInfo.JsonConverter);
|
||||
object? propertyValue = jProperty.Value.Deserialize(typedJsonPropertyInfo.PropertyType, tmpOptions);
|
||||
typedJsonPropertyInfo.PropertyInfo.SetValue(tObject, propertyValue);
|
||||
}
|
||||
else if (TryMatchNArrayIndex(jProperty.Name, out int index))
|
||||
{
|
||||
foreach (var _ in typedJsonProperties.Where(e => e.PropertyIsNArray))
|
||||
{
|
||||
typedJsonPropertyInfo = _;
|
||||
|
||||
Array? propertyValue = typedJsonPropertyInfo.PropertyInfo.GetValue(tObject) as Array;
|
||||
ReflectionHelper.CreateOrExpandArray(ref propertyValue, typedJsonPropertyInfo.PropertyType.GetElementType()!, index + 1);
|
||||
ReflectionHelper.CreateOrExpandArrayElement(propertyValue!, index, (object element) =>
|
||||
{
|
||||
InnerTypedJsonPropertyInfo? insider = GetTypedJsonProperties(element.GetType())
|
||||
.SingleOrDefault(p => string.Equals(p.PropertyName.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, index.ToString()), jProperty.Name));
|
||||
if (insider is not null)
|
||||
{
|
||||
JsonSerializerOptions tmpOptions = GetClonedJsonSerializerOptions(options, insider.JsonConverter);
|
||||
object? elementPropertyValue = JsonSerializer.Deserialize(jProperty.Value, insider.PropertyType, tmpOptions)!;
|
||||
insider.PropertyInfo.SetValue(element, elementPropertyValue);
|
||||
}
|
||||
|
||||
return element;
|
||||
});
|
||||
|
||||
typedJsonPropertyInfo.PropertyInfo.SetValue(tObject, propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tObject;
|
||||
}
|
||||
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
foreach (InnerTypedJsonPropertyInfo typedJsonProperty in GetTypedJsonProperties(value.GetType()))
|
||||
{
|
||||
if (!typedJsonProperty.PropertyIsNArray)
|
||||
{
|
||||
string propertyName = typedJsonProperty.PropertyName;
|
||||
object? propertyValue = typedJsonProperty.PropertyInfo.GetValue(value);
|
||||
if (propertyValue is null)
|
||||
{
|
||||
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.Always || options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull)
|
||||
continue;
|
||||
}
|
||||
if (propertyValue == (typedJsonProperty.PropertyType.IsValueType ? Activator.CreateInstance(typedJsonProperty.PropertyType) : null))
|
||||
{
|
||||
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.Always || options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingDefault)
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonSerializerOptions tmpOptions = GetClonedJsonSerializerOptions(options, typedJsonProperty.JsonConverter);
|
||||
if (typedJsonProperty.JsonConverter is not null && ReflectionHelper.CheckTypeIsSubclassOf(typedJsonProperty.JsonConverter.GetType(), typeof(StringifiedObjectInJsonFormatConverter)))
|
||||
{
|
||||
// TODO: 优化
|
||||
tmpOptions.Converters.Remove(typedJsonProperty.JsonConverter);
|
||||
writer.WritePropertyName(tmpOptions.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
|
||||
writer.WriteStringValue(JsonSerializer.Serialize(propertyValue, tmpOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WritePropertyName(tmpOptions.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
|
||||
writer.WriteRawValue(JsonSerializer.Serialize(propertyValue, tmpOptions), skipInputValidation: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Array? array = (Array?)typedJsonProperty.PropertyInfo.GetValue(value);
|
||||
if (array is null)
|
||||
continue;
|
||||
|
||||
for (int i = 0, len = array.Length; i < len; i++)
|
||||
{
|
||||
object? element = array.GetValue(i);
|
||||
if (element is null)
|
||||
continue;
|
||||
|
||||
JsonSerializerOptions tmpOptions = GetClonedJsonSerializerOptions(options, GetTypedJsonConverter(element.GetType()));
|
||||
JsonObject jSubObject = JsonSerializer.SerializeToNode(element, tmpOptions)!.AsObject();
|
||||
foreach (KeyValuePair<string, JsonNode?> jSubProperty in jSubObject)
|
||||
{
|
||||
string subPropertyName = jSubProperty.Key.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, i.ToString());
|
||||
JsonNode? subPropertyValue = jSubProperty.Value;
|
||||
writer.WritePropertyName(tmpOptions.PropertyNamingPolicy?.ConvertName(subPropertyName) ?? subPropertyName);
|
||||
writer.WriteRawValue(subPropertyValue?.ToJsonString(tmpOptions)!, skipInputValidation: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static InnerTypedJsonPropertyInfo[] GetTypedJsonProperties(Type type)
|
||||
{
|
||||
if (type is null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
string mappedKey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
|
||||
InnerTypedJsonPropertyInfo[]? mappedValue = (InnerTypedJsonPropertyInfo[]?)_mappedTypeJsonProperties[mappedKey];
|
||||
|
||||
if (mappedValue is null)
|
||||
{
|
||||
mappedValue = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p =>
|
||||
{
|
||||
if (p.CanWrite || p.GetCustomAttributes<JsonIncludeAttribute>(inherit: true).Any())
|
||||
{
|
||||
return !p.GetCustomAttributes<JsonIgnoreAttribute>(inherit: false).Any();
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.Select(p =>
|
||||
{
|
||||
string name = p.GetCustomAttribute<JsonPropertyNameAttribute>(inherit: true)?.Name ?? p.Name;
|
||||
return new InnerTypedJsonPropertyInfo
|
||||
(
|
||||
propertyName: name,
|
||||
propertyInfo: p,
|
||||
propertyIsNArray: PROPERTY_NAME_NARRAY.Equals(name) && p.PropertyType.IsArray && p.PropertyType.GetElementType()!.IsClass,
|
||||
jsonConverter: GetTypedJsonConverter(p)
|
||||
);
|
||||
})
|
||||
.OrderBy(e => e.PropertyInfo.GetCustomAttribute<JsonPropertyOrderAttribute>(inherit: true)?.Order)
|
||||
.ToArray();
|
||||
_mappedTypeJsonProperties[mappedKey] = mappedValue;
|
||||
}
|
||||
|
||||
return mappedValue;
|
||||
}
|
||||
|
||||
private static JsonConverter? GetTypedJsonConverter(MemberInfo? memberInfo)
|
||||
{
|
||||
if (memberInfo is null)
|
||||
return null;
|
||||
|
||||
return memberInfo.GetCustomAttributes<JsonConverterAttribute>(inherit: true)
|
||||
.OrderByDescending(attr => attr.IsDefaultAttribute())
|
||||
.Select(attr =>
|
||||
{
|
||||
JsonConverter? converter = null;
|
||||
|
||||
if (memberInfo is Type type)
|
||||
{
|
||||
converter = attr.CreateConverter(type);
|
||||
}
|
||||
else if (memberInfo is PropertyInfo propertyInfo)
|
||||
{
|
||||
converter = attr.CreateConverter(propertyInfo.PropertyType);
|
||||
}
|
||||
|
||||
if (converter is null && attr.ConverterType is not null)
|
||||
{
|
||||
converter = (JsonConverter)Activator.CreateInstance(attr.ConverterType)!;
|
||||
}
|
||||
|
||||
return converter;
|
||||
})
|
||||
.FirstOrDefault(converter => converter is not null);
|
||||
}
|
||||
|
||||
private static JsonSerializerOptions GetClonedJsonSerializerOptions(JsonSerializerOptions options, JsonConverter? converter)
|
||||
{
|
||||
JsonSerializerOptions optionsCopy = options;
|
||||
|
||||
if (converter is not null)
|
||||
{
|
||||
optionsCopy = new JsonSerializerOptions(options);
|
||||
optionsCopy.Converters.Add(converter);
|
||||
}
|
||||
|
||||
return optionsCopy;
|
||||
}
|
||||
|
||||
public const string FLATTEN_PROPERTY_JSON_NAME = "#n";
|
||||
|
||||
private static bool TryMatchNArrayIndex(string key, out int index)
|
||||
{
|
||||
Regex regex = new Regex(@"(_)(\d+)", RegexOptions.Compiled);
|
||||
Regex regex = new Regex(@"_(\d+)$", RegexOptions.Compiled);
|
||||
if (regex.IsMatch(key))
|
||||
{
|
||||
string str = regex.Match(key).Groups[2].Value;
|
||||
string str = regex.Match(key).Groups[1].Value;
|
||||
index = int.Parse(str);
|
||||
return true;
|
||||
}
|
||||
@@ -269,5 +28,356 @@ namespace System.Text.Json.Converters
|
||||
index = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 `<see cref="TModelContainer"/>` 类下需要扁平展开的属性,该属性需是一个数组类型、其中每个元素是一个 `<see cref="TFlattenElement"/>`。
|
||||
/// </summary>
|
||||
protected abstract PropertyInfo FlattenProperty { get; }
|
||||
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
if (!FlattenProperty.PropertyType.IsArray)
|
||||
throw new NotSupportedException();
|
||||
|
||||
return typeToConvert.IsClass && !typeToConvert.IsAbstract && !typeToConvert.IsInterface
|
||||
&& typeof(TModelContainer).IsAssignableFrom(typeToConvert);
|
||||
}
|
||||
|
||||
public override TModelContainer? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.StartObject)
|
||||
{
|
||||
TModelContainer outputObj = ReflectionHelper.CreateInstance<TModelContainer>();
|
||||
|
||||
// 读取为 JsonObject
|
||||
JsonObject jObject = JsonObject.Parse(ref reader, new JsonNodeOptions() { PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive })!.AsObject();
|
||||
if (!jObject.Any())
|
||||
return outputObj;
|
||||
|
||||
// 取得下标最大值(-1 表示无需扁平展开)
|
||||
int maxIndex = jObject.Select(e => TryMatchNArrayIndex(e.Key, out int index) ? index : -1).Max();
|
||||
|
||||
// 解析 JSON 结构
|
||||
JsonPropertyTypeInfo[] jsonPropertyTypes = JsonPropertyTypeInfoCache.GetOrAddJsonPropertyTypes(typeToConvert, options);
|
||||
|
||||
// 遍历 JsonObject 并反序列化
|
||||
foreach (KeyValuePair<string, JsonNode?> jProp in jObject)
|
||||
{
|
||||
string propertyName = jProp.Key;
|
||||
|
||||
if (maxIndex != -1 && TryMatchNArrayIndex(propertyName, out int index))
|
||||
{
|
||||
// 反序列化需扁平展开的字段
|
||||
JsonPropertyTypeInfo[] flattenJsonPropertyTypes = JsonPropertyTypeInfoCache.GetOrAddJsonPropertyTypes(FlattenProperty.PropertyType.GetElementType()!, options);
|
||||
JsonPropertyTypeInfo? jsonPropertyType = flattenJsonPropertyTypes.FirstOrDefault(e => propertyName == e.JsonPropertyName.Replace("$n", index.ToString()));
|
||||
if (jsonPropertyType is null)
|
||||
{
|
||||
if (options.UnmappedMemberHandling == JsonUnmappedMemberHandling.Disallow)
|
||||
throw new JsonException($"The JSON property '{propertyName}' could not be mapped to any .NET member contained in type '{typeToConvert.FullName}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Array? array = ReflectionHelper.GetPropertyValue<Array>(outputObj, FlattenProperty);
|
||||
if (array is null)
|
||||
{
|
||||
array = new TFlattenElement[maxIndex + 1];
|
||||
ReflectionHelper.SetPropertyValue(outputObj, FlattenProperty, array);
|
||||
}
|
||||
|
||||
TFlattenElement? element = ReflectionHelper.GetElementValue<TFlattenElement>(array, index);
|
||||
if (element is null)
|
||||
{
|
||||
element = ReflectionHelper.CreateInstance<TFlattenElement>();
|
||||
ReflectionHelper.SetElementValue(array, index, element);
|
||||
}
|
||||
|
||||
object? elementPropertyValue = jProp.Value?.Deserialize(jsonPropertyType.PropertyType, CopyJsonSerializerOptions(options, jsonPropertyType.JsonConverter));
|
||||
ReflectionHelper.SetPropertyValue(element, jsonPropertyType.Property, elementPropertyValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonPropertyTypeInfo? jsonPropertyType = jsonPropertyTypes.FirstOrDefault(e => propertyName == e.JsonPropertyName);
|
||||
if (jsonPropertyType is null && options.PropertyNameCaseInsensitive)
|
||||
jsonPropertyType = jsonPropertyTypes.FirstOrDefault(e => string.Equals(propertyName, e.JsonPropertyName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// 反序列化普通字段
|
||||
if (jsonPropertyType is not null)
|
||||
{
|
||||
object? propertyValue = jProp.Value?.Deserialize(jsonPropertyType.PropertyType, CopyJsonSerializerOptions(options, jsonPropertyType.JsonConverter));
|
||||
ReflectionHelper.SetPropertyValue(outputObj, jsonPropertyType.Property, propertyValue);
|
||||
}
|
||||
// 反序列化扩展字段
|
||||
else if (jsonPropertyTypes.Any(e => e.IsExtensionData))
|
||||
{
|
||||
throw new NotSupportedException($"'{GetType().FullName}' does not support JsonExtensionData.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outputObj;
|
||||
}
|
||||
|
||||
throw new JsonException($"Unexpected JSON token type '{reader.TokenType}' when reading.");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TModelContainer? inputObj, JsonSerializerOptions options)
|
||||
{
|
||||
if (inputObj is null)
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
// 解析 JSON 结构
|
||||
JsonPropertyTypeInfo[] jsonPropertyTypes = JsonPropertyTypeInfoCache.GetOrAddJsonPropertyTypes(inputObj.GetType(), options);
|
||||
|
||||
// 序列化普通字段
|
||||
foreach (JsonPropertyTypeInfo jsonPropertyType in jsonPropertyTypes)
|
||||
{
|
||||
if (jsonPropertyType.IsExtensionData)
|
||||
continue;
|
||||
|
||||
string propertyName = jsonPropertyType.JsonPropertyName;
|
||||
if (FLATTEN_PROPERTY_JSON_NAME.Equals(propertyName))
|
||||
{
|
||||
if (jsonPropertyType.Property != FlattenProperty)
|
||||
throw new JsonException("Ambiguous flatten property found.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
object? propertyValue = ReflectionHelper.GetPropertyValue<object>(inputObj, jsonPropertyType.Property);
|
||||
if (propertyValue is null)
|
||||
{
|
||||
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.Always || options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull)
|
||||
continue;
|
||||
}
|
||||
else if (propertyValue == (jsonPropertyType.PropertyType.IsValueType ? ReflectionHelper.CreateInstance<TFlattenElement>() : null))
|
||||
{
|
||||
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.Always || options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingDefault)
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
|
||||
writer.WriteRawValue(JsonSerializer.Serialize(propertyValue, CopyJsonSerializerOptions(options, jsonPropertyType.JsonConverter)), skipInputValidation: true);
|
||||
}
|
||||
|
||||
// 序列化扩展字段
|
||||
if (jsonPropertyTypes.Any(e => e.IsExtensionData))
|
||||
{
|
||||
throw new NotSupportedException($"'{GetType().FullName}' does not support JsonExtensionData.");
|
||||
}
|
||||
|
||||
// 序列化需扁平展开的字段
|
||||
Array? flattenArray = ReflectionHelper.GetPropertyValue<Array>(inputObj, FlattenProperty);
|
||||
if (flattenArray is not null)
|
||||
{
|
||||
for (int index = 0; index < flattenArray.Length; index++)
|
||||
{
|
||||
object? element = ReflectionHelper.GetElementValue<object>(flattenArray, index);
|
||||
if (element is null)
|
||||
continue;
|
||||
|
||||
JsonObject jObject = JsonSerializer.SerializeToNode(element, CopyJsonSerializerOptions(options))!.AsObject();
|
||||
foreach (KeyValuePair<string, JsonNode?> jProp in jObject)
|
||||
{
|
||||
string propertyName = jProp.Key.Replace("$n", index.ToString());
|
||||
JsonNode? propertyValueNode = jProp.Value;
|
||||
if (propertyValueNode is null || propertyValueNode.GetValueKind() == JsonValueKind.Null)
|
||||
{
|
||||
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.Never)
|
||||
{
|
||||
writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
|
||||
writer.WriteNullValue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WritePropertyName(options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);
|
||||
writer.WriteRawValue(propertyValueNode?.ToJsonString(options)!, skipInputValidation: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private JsonSerializerOptions CopyJsonSerializerOptions(JsonSerializerOptions options, params JsonConverter?[] customConverters)
|
||||
{
|
||||
JsonSerializerOptions optionsCopy = new JsonSerializerOptions(options);
|
||||
optionsCopy.Converters.Remove(this);
|
||||
|
||||
foreach (JsonConverter? converter in customConverters)
|
||||
{
|
||||
if (converter is not null)
|
||||
optionsCopy.Converters.Add(converter);
|
||||
}
|
||||
|
||||
return optionsCopy;
|
||||
}
|
||||
}
|
||||
|
||||
partial class FlattenNArrayObjectConverterBase<TModelContainer, TFlattenElement>
|
||||
{
|
||||
private class JsonPropertyTypeInfo
|
||||
{
|
||||
public PropertyInfo Property { get; }
|
||||
|
||||
public Type PropertyType { get { return Property.PropertyType; } }
|
||||
|
||||
public JsonIgnoreCondition JsonIgnoreCondition { get; }
|
||||
|
||||
public JsonConverter? JsonConverter { get; }
|
||||
|
||||
public string JsonPropertyName { get; }
|
||||
|
||||
public int? JsonPropertyOrder { get; }
|
||||
|
||||
public bool IsExtensionData { get; }
|
||||
|
||||
public JsonPropertyTypeInfo(PropertyInfo property, JsonIgnoreCondition jsonIgnoreCondition, JsonConverter? jsonConverter, string jsonPropertyName, int? jsonPropertyOrder)
|
||||
{
|
||||
Property = property ?? throw new ArgumentNullException(nameof(property));
|
||||
JsonIgnoreCondition = jsonIgnoreCondition;
|
||||
JsonConverter = jsonConverter;
|
||||
JsonPropertyName = jsonPropertyName;
|
||||
JsonPropertyOrder = jsonPropertyOrder;
|
||||
IsExtensionData = property.IsDefined(typeof(JsonExtensionDataAttribute), inherit: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JsonPropertyTypeInfoCache
|
||||
{
|
||||
private static readonly Hashtable _cache = new Hashtable(capacity: 32);
|
||||
|
||||
private static Type[] GetSortedTypeHierarchy(Type type)
|
||||
{
|
||||
if (!type.IsInterface)
|
||||
{
|
||||
IList<Type> results = new List<Type>();
|
||||
for (Type? current = type; current is not null; current = current.BaseType)
|
||||
{
|
||||
results.Add(current);
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static PropertyInfo[] GetSerializableProperties(Type typeToConvert)
|
||||
{
|
||||
IList<PropertyInfo> results = new List<PropertyInfo>();
|
||||
|
||||
foreach (PropertyInfo propertyInfo in typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
if (propertyInfo.GetIndexParameters().Length > 0 ||
|
||||
(propertyInfo.GetMethod?.IsAbstract == true || propertyInfo.SetMethod?.IsAbstract == true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasJsonIncludeAttribute = propertyInfo.GetCustomAttribute<JsonIncludeAttribute>(inherit: false) is not null;
|
||||
if (propertyInfo.GetMethod?.IsPublic == true ||
|
||||
propertyInfo.SetMethod?.IsPublic == true ||
|
||||
hasJsonIncludeAttribute)
|
||||
{
|
||||
results.Add(propertyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
private static JsonIgnoreCondition GetPropertyJsonIgnoreCondition(PropertyInfo propertyInfo, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
JsonIgnoreAttribute? ignoreAttribute = propertyInfo.GetCustomAttribute<JsonIgnoreAttribute>(inherit: false);
|
||||
if (ignoreAttribute is not null)
|
||||
return ignoreAttribute.Condition;
|
||||
|
||||
return jsonSerializerOptions.DefaultIgnoreCondition;
|
||||
}
|
||||
|
||||
private static JsonConverter? GetPropertyJsonConverter(PropertyInfo propertyInfo, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
JsonConverterAttribute? converterAttribute = propertyInfo.GetCustomAttribute<JsonConverterAttribute>(inherit: false);
|
||||
if (converterAttribute is null)
|
||||
return null;
|
||||
|
||||
Type? converterType = converterAttribute.ConverterType;
|
||||
if (converterType is null)
|
||||
return converterAttribute.CreateConverter(propertyInfo.PropertyType)
|
||||
?? converterAttribute.CreateConverter(propertyInfo.ReflectedType ?? propertyInfo.DeclaringType!)
|
||||
?? jsonSerializerOptions.GetConverter(propertyInfo.PropertyType);
|
||||
else
|
||||
return (JsonConverter)Activator.CreateInstance(converterType)!;
|
||||
}
|
||||
|
||||
private static int? GetPropertyJsonOrder(PropertyInfo propertyInfo, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
JsonPropertyOrderAttribute? orderAttribute = propertyInfo.GetCustomAttribute<JsonPropertyOrderAttribute>(inherit: true);
|
||||
if (orderAttribute is not null)
|
||||
return orderAttribute.Order;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetPropertyJsonName(PropertyInfo propertyInfo, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
JsonPropertyNameAttribute? nameAttribute = propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>(inherit: true);
|
||||
if (nameAttribute is not null)
|
||||
return nameAttribute.Name;
|
||||
else if (jsonSerializerOptions.PropertyNamingPolicy is not null)
|
||||
return jsonSerializerOptions.PropertyNamingPolicy.ConvertName(propertyInfo.Name);
|
||||
else
|
||||
return propertyInfo.Name;
|
||||
}
|
||||
|
||||
public static JsonPropertyTypeInfo[] GetOrAddJsonPropertyTypes(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
JsonPropertyTypeInfo[]? results = _cache[typeToConvert] as JsonPropertyTypeInfo[];
|
||||
|
||||
if (results is null)
|
||||
{
|
||||
IList<JsonPropertyTypeInfo> temp = new List<JsonPropertyTypeInfo>();
|
||||
|
||||
foreach (Type currentType in GetSortedTypeHierarchy(typeToConvert))
|
||||
{
|
||||
if (currentType == typeof(Object) ||
|
||||
currentType == typeof(ValueType))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (PropertyInfo propertyInfo in GetSerializableProperties(typeToConvert))
|
||||
{
|
||||
JsonIgnoreCondition jsonIgnoreCondition = GetPropertyJsonIgnoreCondition(propertyInfo, options);
|
||||
JsonConverter? jsonConverter = GetPropertyJsonConverter(propertyInfo, options);
|
||||
string jsonPropertyName = GetPropertyJsonName(propertyInfo, options);
|
||||
int? jsonPropertyOrder = GetPropertyJsonOrder(propertyInfo, options);
|
||||
|
||||
if (!temp.Any(e => e.JsonPropertyName == jsonPropertyName))
|
||||
temp.Add(new JsonPropertyTypeInfo(property: propertyInfo, jsonIgnoreCondition: jsonIgnoreCondition, jsonConverter: jsonConverter, jsonPropertyName: jsonPropertyName, jsonPropertyOrder: jsonPropertyOrder));
|
||||
}
|
||||
}
|
||||
|
||||
results = temp.OrderBy(e => e.JsonPropertyOrder).ToArray();
|
||||
_cache[typeToConvert] = results;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Events
|
||||
{
|
||||
@@ -38,15 +39,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Events
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class EventClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<OrderEvent>
|
||||
internal class EventClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<OrderEvent, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => OrderEvent._flattenProperty;
|
||||
}
|
||||
|
||||
internal class EventClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<OrderEvent>
|
||||
internal class EventClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<OrderEvent, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => OrderEvent._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(OrderEvent).GetProperty(nameof(CouponList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置子商户号。
|
||||
/// </summary>
|
||||
@@ -161,8 +166,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Events
|
||||
/// <summary>
|
||||
/// 获取或设置代金券使用列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[Newtonsoft.Json.JsonProperty(Converters.EventClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.EventClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Coupon[]? CouponList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Events
|
||||
{
|
||||
@@ -18,15 +19,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Events
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class EventClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<PAPOrderEvent>
|
||||
internal class EventClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<PAPOrderEvent, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => PAPOrderEvent._flattenProperty;
|
||||
}
|
||||
|
||||
internal class EventClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<PAPOrderEvent>
|
||||
internal class EventClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<PAPOrderEvent, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => PAPOrderEvent._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(PAPOrderEvent).GetProperty(nameof(CouponList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置子商户号。
|
||||
/// </summary>
|
||||
@@ -119,8 +124,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Events
|
||||
/// <summary>
|
||||
/// 获取或设置代金券使用列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[Newtonsoft.Json.JsonProperty(Converters.EventClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.EventClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Coupon[]? CouponList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
@@ -113,15 +114,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<QueryMerchantCustomsCustomDeclarationResponse>
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<QueryMerchantCustomsCustomDeclarationResponse, Types.Record>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => QueryMerchantCustomsCustomDeclarationResponse._flattenProperty;
|
||||
}
|
||||
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<QueryMerchantCustomsCustomDeclarationResponse>
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<QueryMerchantCustomsCustomDeclarationResponse, Types.Record>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => QueryMerchantCustomsCustomDeclarationResponse._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(QueryMerchantCustomsCustomDeclarationResponse).GetProperty(nameof(RecordList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
@@ -154,8 +159,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
/// <summary>
|
||||
/// 获取或设置记录列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[Newtonsoft.Json.JsonProperty(Converters.ResponseClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.ResponseClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Record[] RecordList { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,3 +1,5 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
/// <summary>
|
||||
@@ -9,7 +11,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Coupon
|
||||
public class RefundCoupon
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置代金券 ID。
|
||||
@@ -36,15 +38,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<CreatePayRefundResponse>
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<CreatePayRefundResponse, Types.RefundCoupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => CreatePayRefundResponse._flattenProperty;
|
||||
}
|
||||
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<CreatePayRefundResponse>
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<CreatePayRefundResponse, Types.RefundCoupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => CreatePayRefundResponse._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(CreatePayRefundResponse).GetProperty(nameof(CouponRefundList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
@@ -167,9 +173,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
/// <summary>
|
||||
/// 获取或设置代金券退款列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
public Types.Coupon[]? CouponRefundList { get; set; }
|
||||
[Newtonsoft.Json.JsonProperty(Converters.ResponseClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.ResponseClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.RefundCoupon[]? CouponRefundList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置现金支付金额(单位:分)。
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
@@ -227,15 +228,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<GetPayOrderResponse>
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<GetPayOrderResponse, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetPayOrderResponse._flattenProperty;
|
||||
}
|
||||
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<GetPayOrderResponse>
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<GetPayOrderResponse, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetPayOrderResponse._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(GetPayOrderResponse).GetProperty(nameof(CouponList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
@@ -381,8 +386,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
/// <summary>
|
||||
/// 获取或设置代金券使用列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[Newtonsoft.Json.JsonProperty(Converters.ResponseClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.ResponseClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Coupon[]? CouponList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -447,9 +452,5 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
[System.Text.Json.Serialization.JsonPropertyName("promotion_detail")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.StringifiedObjectInJsonFormatConverter))]
|
||||
public Types.PromotionDetail? PromotionDetail { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty]
|
||||
[Newtonsoft.Json.JsonExtensionData]
|
||||
public IDictionary<string, Newtonsoft.Json.Linq.JToken>? ExtensionData { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
@@ -92,15 +93,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<GetPayRefundResponse>
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<GetPayRefundResponse, Types.Refund>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetPayRefundResponse._flattenProperty;
|
||||
}
|
||||
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<GetPayRefundResponse>
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<GetPayRefundResponse, Types.Refund>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetPayRefundResponse._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(GetPayRefundResponse).GetProperty(nameof(RefundList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
@@ -185,8 +190,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
/// <summary>
|
||||
/// 获取或设置记录列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[Newtonsoft.Json.JsonProperty(Converters.ResponseClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.ResponseClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Refund[] RefundList { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
@@ -18,15 +19,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
|
||||
internal static class Converters
|
||||
{
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<GetTransitOrderResponse>
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<GetTransitOrderResponse, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetTransitOrderResponse._flattenProperty;
|
||||
}
|
||||
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<GetTransitOrderResponse>
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<GetTransitOrderResponse, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetTransitOrderResponse._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(GetTransitOrderResponse).GetProperty(nameof(CouponList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
@@ -142,8 +147,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
/// <summary>
|
||||
/// 获取或设置代金券使用列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[Newtonsoft.Json.JsonProperty(Converters.ResponseClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.ResponseClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Coupon[]? CouponList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
{
|
||||
/// <summary>
|
||||
@@ -5,7 +8,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponseClassNewtonsoftJsonConverter))]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponseClassSystemTextJsonConverter))]
|
||||
public class GetTransitPartnerOrderResponse : GetTransitOrderResponse
|
||||
public class GetTransitPartnerOrderResponse : WechatTenpaySignableResponse
|
||||
{
|
||||
public static new class Types
|
||||
{
|
||||
@@ -14,17 +17,43 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
}
|
||||
}
|
||||
|
||||
internal static new class Converters
|
||||
internal static class Converters
|
||||
{
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<GetTransitPartnerOrderResponse>
|
||||
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.Internal.FlattenNArrayObjectConverterBase<GetTransitPartnerOrderResponse, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetTransitPartnerOrderResponse._flattenProperty;
|
||||
}
|
||||
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<GetTransitPartnerOrderResponse>
|
||||
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Serialization.Internal.FlattenNArrayObjectConverterBase<GetTransitPartnerOrderResponse, Types.Coupon>
|
||||
{
|
||||
protected override PropertyInfo FlattenProperty => GetTransitPartnerOrderResponse._flattenProperty;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static PropertyInfo _flattenProperty = typeof(GetTransitPartnerOrderResponse).GetProperty(nameof(CouponList), BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("mch_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("mch_id")]
|
||||
#pragma warning disable CS8618
|
||||
#pragma warning disable CS8765
|
||||
public override string MerchantId { get; set; }
|
||||
#pragma warning restore CS8765
|
||||
#pragma warning restore CS8618
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("appid")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("appid")]
|
||||
#pragma warning disable CS8618
|
||||
#pragma warning disable CS8765
|
||||
public override string AppId { get; set; }
|
||||
#pragma warning restore CS8765
|
||||
#pragma warning restore CS8618
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置子商户号。
|
||||
/// </summary>
|
||||
@@ -39,11 +68,146 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
|
||||
[System.Text.Json.Serialization.JsonPropertyName("sub_appid")]
|
||||
public string? SubAppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置终端设备号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("device_info")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("device_info")]
|
||||
public string? DeviceInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用户唯一标识。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("openid")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("openid")]
|
||||
public string OpenId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用户是否订阅该公众号标识。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("is_subscribe")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Internal.YesOrNoBooleanConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("is_subscribe")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Internal.YesOrNoBooleanConverter))]
|
||||
public bool? IsSubscribed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置交易状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("trade_state")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("trade_state")]
|
||||
public string TradeState { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置交易状态描述。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("trade_state_desc")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("trade_state_desc")]
|
||||
public string TradeStateDescription { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置交易类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("trade_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("trade_type")]
|
||||
public string TradeType { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置付款银行。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_type")]
|
||||
public string BankType { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置订单金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("total_fee")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("total_fee")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||
public int TotalFee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置货币类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("fee_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("fee_type")]
|
||||
public string? FeeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置应结订单金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("settlement_total_fee")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("settlement_total_fee")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||
public int? SettlementFee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置代金券金额。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("coupon_fee")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("coupon_fee")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||
public int? CouponFee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置代金券使用数量。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("coupon_count")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("coupon_count")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||
public int? CouponCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置代金券使用列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
|
||||
public new Types.Coupon[]? CouponList { get; set; }
|
||||
[Newtonsoft.Json.JsonProperty(Converters.ResponseClassNewtonsoftJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(Converters.ResponseClassSystemTextJsonConverter.FLATTEN_PROPERTY_JSON_NAME)]
|
||||
public Types.Coupon[]? CouponList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置现金支付金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("cash_fee")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("cash_fee")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||
public int? CashFee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置现金支付货币类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("cash_fee_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("cash_fee_type")]
|
||||
public string? CashFeeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置商户订单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_trade_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
|
||||
public string OutTradeNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付订单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("transaction_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
|
||||
public string TransactionId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置附加数据。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("attach")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("attach")]
|
||||
public string? Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置支付完成时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("time_end")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.DigitalDateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("time_end")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.DigitalDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? EndTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -1,74 +1,92 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities
|
||||
{
|
||||
internal static class ReflectionHelper
|
||||
{
|
||||
public static void CreateOrExpandArray(ref Array? array, Type elementType, int capacity)
|
||||
{
|
||||
if (elementType is null) throw new ArgumentNullException(nameof(elementType));
|
||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
private readonly static Hashtable _ctorCache = new Hashtable(capacity: 128);
|
||||
private readonly static Hashtable _getterCache = new Hashtable(capacity: 128);
|
||||
private readonly static Hashtable _setterCache = new Hashtable(capacity: 128);
|
||||
|
||||
if (array is null)
|
||||
public static T CreateInstance<T>() where T : new()
|
||||
{
|
||||
// 提供比 new T() 或 Activator.CreateInstance<T>() 更快的无参实例化方法
|
||||
// 只可针对热点类型使用,否则可能会更慢
|
||||
|
||||
Type type = typeof(T);
|
||||
|
||||
Func<T>? ctor = _ctorCache[type] as Func<T>;
|
||||
if (ctor is null)
|
||||
{
|
||||
array = Array.CreateInstance(elementType, capacity);
|
||||
}
|
||||
else if (array.Length < capacity)
|
||||
{
|
||||
Array tmpArray = Array.CreateInstance(elementType, capacity);
|
||||
Array.Copy(array, tmpArray, array.Length);
|
||||
array = tmpArray;
|
||||
ctor = Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
|
||||
_ctorCache[type] = ctor;
|
||||
}
|
||||
|
||||
return ctor.Invoke();
|
||||
}
|
||||
|
||||
public static void CreateOrExpandArrayElement(Array array, int index, Func<object, object>? updateFactory)
|
||||
public static T? GetPropertyValue<T>(object targetObj, PropertyInfo property)
|
||||
{
|
||||
if (array is null) throw new ArgumentNullException(nameof(array));
|
||||
if (index < 0 || index >= array.Length) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (updateFactory is null) throw new ArgumentNullException(nameof(updateFactory));
|
||||
// 提供比 PropertyInfo.GetValue() 更快的属性取值方法
|
||||
// 只可针对热点类型使用,否则可能会更慢
|
||||
|
||||
object? element = array.GetValue(index);
|
||||
if (element is null)
|
||||
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||
if (property is null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
if (!property.CanRead)
|
||||
throw new InvalidOperationException($"Property '{property.Name}' of type '{typeof(T).FullName}' does not have a getter.");
|
||||
|
||||
Func<object, T>? getter = _getterCache[property] as Func<object, T>;
|
||||
if (getter is null)
|
||||
{
|
||||
Type elementType = array.GetType().GetElementType()!;
|
||||
|
||||
if (elementType.IsAbstract || elementType.IsInterface)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (elementType.IsArray)
|
||||
{
|
||||
element = Array.CreateInstance(elementType, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
element = Activator.CreateInstance(elementType);
|
||||
}
|
||||
ParameterExpression targetExpr = Expression.Parameter(typeof(object));
|
||||
UnaryExpression castTargetExpr = Expression.Convert(targetExpr, targetObj.GetType());
|
||||
MemberExpression getPropertyValueExpr = Expression.Property(castTargetExpr, property);
|
||||
UnaryExpression castPropertyValueExpr = Expression.Convert(getPropertyValueExpr, typeof(T));
|
||||
getter = Expression.Lambda<Func<object, T>>(castPropertyValueExpr, targetExpr).Compile();
|
||||
_getterCache[property] = getter;
|
||||
}
|
||||
|
||||
element = updateFactory(element!);
|
||||
return getter.Invoke(targetObj);
|
||||
}
|
||||
|
||||
public static void SetPropertyValue<T>(object targetObj, PropertyInfo property, T? value)
|
||||
{
|
||||
// 提供比 PropertyInfo.SetValue() 更快的属性赋值方法
|
||||
// 只可针对热点类型使用,否则可能会更慢
|
||||
|
||||
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||
if (property is null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
if (!property.CanWrite)
|
||||
throw new InvalidOperationException($"Property '{property.Name}' of type '{typeof(T).FullName}' does not have a setter.");
|
||||
|
||||
Action<object, T?>? setter = _setterCache[property] as Action<object, T?>;
|
||||
if (setter is null)
|
||||
{
|
||||
ParameterExpression targetExpr = Expression.Parameter(typeof(object));
|
||||
ParameterExpression propertyValueExpr = Expression.Parameter(typeof(T));
|
||||
UnaryExpression castTargetExpr = Expression.Convert(targetExpr, targetObj.GetType());
|
||||
UnaryExpression castPropertyValueExpr = Expression.Convert(propertyValueExpr, property.PropertyType);
|
||||
MethodCallExpression setPropertyValueExpr = Expression.Call(castTargetExpr, property.GetSetMethod()!, castPropertyValueExpr);
|
||||
setter = Expression.Lambda<Action<object, T?>>(setPropertyValueExpr, targetExpr, propertyValueExpr).Compile();
|
||||
_setterCache[property] = setter;
|
||||
}
|
||||
|
||||
setter.Invoke(targetObj, value);
|
||||
}
|
||||
|
||||
public static T? GetElementValue<T>(Array array, int index)
|
||||
{
|
||||
return (T?)array.GetValue(index);
|
||||
}
|
||||
|
||||
public static void SetElementValue<T>(Array array, int index, T? element)
|
||||
{
|
||||
array.SetValue(element, index);
|
||||
}
|
||||
|
||||
public static bool CheckTypeIsSubclassOf(Type childType, Type baseType)
|
||||
{
|
||||
bool IsTheRawGenericType(Type type)
|
||||
=> baseType == (type.IsGenericType ? type.GetGenericTypeDefinition() : type);
|
||||
|
||||
bool isTheRawGenericType = childType.GetInterfaces().Any(IsTheRawGenericType);
|
||||
if (isTheRawGenericType)
|
||||
return true;
|
||||
|
||||
Type? t = childType;
|
||||
while (t is not null && t != typeof(object))
|
||||
{
|
||||
isTheRawGenericType = IsTheRawGenericType(t);
|
||||
if (isTheRawGenericType) return true;
|
||||
t = t.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -81,19 +81,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests
|
||||
|
||||
Assert.DoesNotContain("#n", actualJson);
|
||||
Assert.DoesNotContain("$n", actualJson);
|
||||
Assert.DoesNotContain("_flattenProperty", actualJson);
|
||||
Assert.DoesNotContain(nameof(Models.QueryMerchantCustomsCustomDeclarationResponse.RecordList), actualJson);
|
||||
|
||||
Assert.Contains("\"return_code\"", actualJson);
|
||||
Assert.Contains("\"RETURN_CODE\"", actualJson);
|
||||
Assert.Contains("\"return_msg\"", actualJson);
|
||||
Assert.Contains("\"RETURN_MSG\"", actualJson);
|
||||
Assert.Contains("\"sub_order_no_0\"", actualJson);
|
||||
Assert.Contains("\"SUB_ORDER_NO_0\"", actualJson);
|
||||
Assert.Contains("\"sub_order_id_0\"", actualJson);
|
||||
Assert.Contains("\"SUB_ORDER_ID_0\"", actualJson);
|
||||
Assert.Contains("\"sub_order_no_1\"", actualJson);
|
||||
Assert.Contains("\"SUB_ORDER_NO_1\"", actualJson);
|
||||
Assert.Contains("\"sub_order_id_1\"", actualJson);
|
||||
Assert.Contains("\"SUB_ORDER_ID_1\"", actualJson);
|
||||
Assert.Contains("\"return_code\":\"RETURN_CODE\"", actualJson);
|
||||
Assert.Contains("\"return_msg\":\"RETURN_MSG\"", actualJson);
|
||||
Assert.Contains("\"appid\":\"APPID\"", actualJson);
|
||||
Assert.Contains("\"mch_id\":\"MCH_ID\"", actualJson);
|
||||
Assert.Contains("\"result_code\":\"RESULT_CODE\"", actualJson);
|
||||
Assert.Contains("\"err_code\":\"ERR_CODE\"", actualJson);
|
||||
Assert.Contains("\"err_code_des\":\"ERR_CODE_DESC\"", actualJson);
|
||||
Assert.Contains("\"transaction_id\":\"TRANSACTION_ID\"", actualJson);
|
||||
Assert.Contains("\"sub_order_no_0\":\"SUB_ORDER_NO_0\"", actualJson);
|
||||
Assert.Contains("\"sub_order_id_0\":\"SUB_ORDER_ID_0\"", actualJson);
|
||||
Assert.Contains("\"mch_customs_no_0\":\"MCH_CUSTOMS_NO_0\"", actualJson);
|
||||
Assert.Contains("\"customs_0\":\"CUSTOMS_0\"", actualJson);
|
||||
Assert.Contains("\"duty_0\":10", actualJson);
|
||||
Assert.Contains("\"fee_type_0\":\"FEE_TYPE_0\"", actualJson);
|
||||
Assert.Contains("\"modify_time_0\":\"20000101112233\"", actualJson);
|
||||
Assert.Contains("\"sub_order_no_1\":\"SUB_ORDER_NO_1\"", actualJson);
|
||||
Assert.Contains("\"sub_order_id_1\":\"SUB_ORDER_ID_1\"", actualJson);
|
||||
Assert.Contains("\"mch_customs_no_1\":\"MCH_CUSTOMS_NO_1\"", actualJson);
|
||||
Assert.Contains("\"customs_1\":\"CUSTOMS_1\"", actualJson);
|
||||
Assert.Contains("\"duty_1\":11", actualJson);
|
||||
Assert.Contains("\"fee_type_1\":\"FEE_TYPE_1\"", actualJson);
|
||||
Assert.Contains("\"modify_time_1\":\"20010101112233\"", actualJson);
|
||||
Assert.Contains("\"count\":2", actualJson);
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user