refactor(tenpayv2): 优化反射性能

This commit is contained in:
Fu Diwei
2024-02-01 20:09:15 +08:00
committed by RHQYZ
parent 5eed125d49
commit 1eef179f0a
12 changed files with 848 additions and 513 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
/// 获取或设置现金支付金额(单位:分)。

View File

@@ -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; }
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
});
}