我有一个带有私有List<T>
属性的类,我想使用JsonSerializer
进行序列化/反序列化。在.NET Core中似乎不支持使用JsonPropertyAttribute
。那么如何将我的私有列表属性进行序列化?
我正在使用System.Text.Json来实现此功能。
我有一个带有私有List<T>
属性的类,我想使用JsonSerializer
进行序列化/反序列化。在.NET Core中似乎不支持使用JsonPropertyAttribute
。那么如何将我的私有列表属性进行序列化?
我正在使用System.Text.Json来实现此功能。
看起来System.Text.Json不支持私有属性序列化。
但正如微软的文档所说,您可以使用自定义转换器来完成此操作。
序列化和反序列化的代码片段;
public class Category
{
public Category(List<string> names)
{
this.Names1 = names;
}
private List<string> Names1 { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
}
public class CategoryJsonConverter : JsonConverter<Category>
{
public override Category Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var name = reader.GetString();
var source = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(name);
var category = new Category(null);
var categoryType = category.GetType();
var categoryProps = categoryType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var s in source.Keys)
{
var categoryProp = categoryProps.FirstOrDefault(x => x.Name == s);
if (categoryProp != null)
{
var value = JsonSerializer.Deserialize(source[s].GetRawText(), categoryProp.PropertyType);
categoryType.InvokeMember(categoryProp.Name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance,
null,
category,
new object[] { value });
}
}
return category;
}
public override void Write(Utf8JsonWriter writer,
Category value,
JsonSerializerOptions options)
{
var props = value.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.ToDictionary(x => x.Name, x => x.GetValue(value));
var ser = JsonSerializer.Serialize(props);
writer.WriteStringValue(ser);
}
}
static void Main(string[] args)
{
Category category = new Category(new List<string>() { "1" });
category.Name2 = "2";
category.Name3 = "3";
var opt = new JsonSerializerOptions
{
Converters = { new CategoryJsonConverter() },
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var json = JsonSerializer.Serialize(category, opt);
var obj = JsonSerializer.Deserialize<Category>(json, opt);
Console.WriteLine(json);
Console.ReadKey();
}
结果;
"{\"Names1\":[\"1\"],\"Name2\":\"2\",\"Name3\":\"3\"}"
序列化所有标记有某些自定义属性的私有属性。
序列化特定类型的所有私有属性。
按名称序列化特定类型的特定私有属性。
[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class JsonIncludePrivatePropertyAttribute : System.Attribute { }
public static partial class JsonExtensions
{
public static Action<JsonTypeInfo> AddPrivateProperties<TAttribute>() where TAttribute : System.Attribute => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
foreach (var type in typeInfo.Type.BaseTypesAndSelf().TakeWhile(b => b != typeof(object)))
AddPrivateProperties(typeInfo, type, p => Attribute.IsDefined(p, typeof(TAttribute)));
};
public static Action<JsonTypeInfo> AddPrivateProperties(Type declaredType) => typeInfo =>
AddPrivateProperties(typeInfo, declaredType, p => true);
public static Action<JsonTypeInfo> AddPrivateProperty(Type declaredType, string propertyName) => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object || !declaredType.IsAssignableFrom(typeInfo.Type))
return;
var propertyInfo = declaredType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic);
if (propertyInfo == null)
throw new ArgumentException(string.Format("Private roperty {0} not found in type {1}", propertyName, declaredType));
if (typeInfo.Properties.Any(p => p.GetMemberInfo() == propertyInfo))
return;
AddProperty(typeInfo, propertyInfo);
};
static void AddPrivateProperties(JsonTypeInfo typeInfo, Type declaredType, Func<PropertyInfo, bool> filter)
{
if (typeInfo.Kind != JsonTypeInfoKind.Object || !declaredType.IsAssignableFrom(typeInfo.Type))
return;
var propertyInfos = declaredType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var propertyInfo in propertyInfos.Where(p => p.GetIndexParameters().Length == 0 && filter(p)))
AddProperty(typeInfo, propertyInfo);
}
static void AddProperty(JsonTypeInfo typeInfo, PropertyInfo propertyInfo)
{
if (propertyInfo.GetIndexParameters().Length > 0)
throw new ArgumentException("Indexed properties are not supported.");
var ignore = propertyInfo.GetCustomAttribute<JsonIgnoreAttribute>();
if (ignore?.Condition == JsonIgnoreCondition.Always)
return;
var name = propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name
?? typeInfo.Options?.PropertyNamingPolicy?.ConvertName(propertyInfo.Name)
?? propertyInfo.Name;
var property = typeInfo.CreateJsonPropertyInfo(propertyInfo.PropertyType, name);
property.Get = CreateGetter(typeInfo.Type, propertyInfo.GetGetMethod(true));
property.Set = CreateSetter(typeInfo.Type, propertyInfo.GetSetMethod(true));
property.AttributeProvider = propertyInfo;
property.CustomConverter = propertyInfo.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType is {} converterType
? (JsonConverter?)Activator.CreateInstance(converterType)
: null;
// TODO: handle ignore?.Condition == JsonIgnoreCondition.Never, WhenWritingDefault, or WhenWritingNull by setting property.ShouldSerialize appropriately
// TODO: handle JsonRequiredAttribute, JsonNumberHandlingAttribute
typeInfo.Properties.Add(property);
}
delegate TValue RefFunc<TObject, TValue>(ref TObject arg);
static Func<object, object?>? CreateGetter(Type type, MethodInfo? method)
{
if (method == null)
return null;
var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateGetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
return (Func<object, object?>)(myMethod.MakeGenericMethod(new[] { type, method.ReturnType }).Invoke(null, new[] { method })!);
}
static Func<object, object?> CreateGetterGeneric<TObject, TValue>(MethodInfo method)
{
if (method == null)
throw new ArgumentNullException();
if(typeof(TObject).IsValueType)
{
// https://dev59.com/rlLTa4cB1Zd3GeqPZVF9
// https://dev59.com/Z0jSa4cB1Zd3GeqPHbDH#1212396
var func = (RefFunc<TObject, TValue>)Delegate.CreateDelegate(typeof(RefFunc<TObject, TValue>), null, method);
return (o) => {var tObj = (TObject)o; return func(ref tObj); };
}
else
{
var func = (Func<TObject, TValue>)Delegate.CreateDelegate(typeof(Func<TObject, TValue>), method);
return (o) => func((TObject)o);
}
}
static Action<object,object?>? CreateSetter(Type type, MethodInfo? method)
{
if (method == null)
return null;
var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateSetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
return (Action<object,object?>)(myMethod.MakeGenericMethod(new [] { type, method.GetParameters().Single().ParameterType }).Invoke(null, new[] { method })!);
}
static Action<object,object?>? CreateSetterGeneric<TObject, TValue>(MethodInfo method)
{
if (method == null)
throw new ArgumentNullException();
if (typeof(TObject).IsValueType)
{
// TODO: find a performant way to do this. Possibilities:
// Box<T> from Microsoft.Toolkit.HighPerformance
// https://dev59.com/KWMk5IYBdhLWcg3w0hI-
return (o, v) => method.Invoke(o, new [] { v });
}
else
{
var func = (Action<TObject, TValue?>)Delegate.CreateDelegate(typeof(Action<TObject, TValue?>), method);
return (o, v) => func((TObject)o, (TValue?)v);
}
}
static MemberInfo? GetMemberInfo(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo);
static IEnumerable<Type> BaseTypesAndSelf(this Type? type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
那么,如果你的模型看起来像:
public partial class Model
{
List<int> PrivateList { get; set; } = new();
[JsonIgnore] // For testing purposes only
public List<int> SurrogateList { get => PrivateList; set => PrivateList = value; }
}
[JsonIncludePrivateProperty]
标记PrivateList
:public partial class Model
{
[JsonIncludePrivateProperty]
List<int> PrivateList { get; set; } = new();
并使用以下选项进行序列化:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.AddPrivateProperties<JsonIncludePrivatePropertyAttribute>() },
},
};
或者如果您无法更改您的模型,您可以将其所有私有属性包含如下:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.AddPrivateProperties(typeof(Model)) },
},
};
或者只返回名为PrivateList
的属性,如下所示:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.AddPrivateProperty(typeof(Model), "PrivateList") },
},
};
使用上述任何选项,生成的JSON将为例如{"PrivateList":[1,2,3]}
。
注意事项:
不建议自动序列化所有类型的私有属性,但如果出于某种原因需要这样做,请使用以下修饰符:
public static Action<JsonTypeInfo> AddPrivateProperties() => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
foreach (var type in typeInfo.Type.BaseTypesAndSelf().TakeWhile(b => b != typeof(object)))
AddPrivateProperties(typeInfo, type, p => true);
};
截至 .NET 7,无法访问 System.Text.Json 的构造函数元数据,因此似乎没有办法将私有属性序列化并将其反序列化为构造函数参数。
有一种类型信息修改器可以导致私有字段被序列化,请参见文档示例 自定义 JSON 合同:示例:序列化私有字段。
可能会在基类和派生类中具有相同名称的私有属性。如果尝试序列化两者的私有属性,则可能会出现异常
System.InvalidOperationException: The JSON property name for 'Type.PropertyName' collides with another property.
如果发生这种情况,您将把其中一个属性映射到另一个名称,例如通过向其中一个添加 [JsonPropertyName("SomeAlternateName")]
。
演示代码片段请点击这里。
根据Microsoft 文档,System.Text.Json 在 .NET 5 开始部分地支持私有属性序列化。
通过
[JsonInclude]
属性,System.Text.Json 支持私有和内部属性的设置和获取。
请注意上述文档中非常具体的措辞。这意味着如果您有以下属性:
private string MyProperty { get; set; }
那么[JsonInclude]
将不起作用。但是,如果您将此属性声明如下:
public string MyProperty { private get; private set; }
然后它将按预期工作。
在这里查找更多详细信息。
'...SubscriptionModel'类型上的'非公共属性'TenantsSource'被注释为无效的'JsonIncludeAttribute'。'。看起来,如果getter或setter中有一个是private,则[JsonInclude]有效,但如果两者都是private则无效。
- Felix虽然您不能直接序列化私有字段,但可以间接地实现。
您需要为该字段提供一个公共属性和一个构造函数,如下面的示例:
class MyNumbers
{
// This private field will not be serialized
private List<int> _numbers;
// This public property will be serialized
public IEnumerable<int> Numbers => _numbers;
// The serialized property will be recovered with this dedicated constructor
// upon deserialization. Type and name must be the same as the public property.
public MyNumbers(IEnumerable<int> Numbers = null)
{
_numbers = Numbers as List<int> ?? Numbers?.ToList() ?? new();
}
}
string json;
// Serialization
{
MyNumbers myNumbers = new(new List<int> { 10, 20, 30});
json = JsonSerializer.Serialize(myNumbers);
Console.WriteLine(json);
}
// Deserialization
{
var myNumbers2 = JsonSerializer.Deserialize<MyNumbers>(json);
foreach (var number in myNumbers2.Numbers)
Console.Write(number + " ");
}
输出:
{"Numbers":[10,20,30]}
10 20 30
如果您希望阻止他人访问您的私人数据,可以将名称更改为明确禁止的名称,例如__private_numbers
。
class MyNumbers2
{
private List<int> _numbers;
public IEnumerable<int> __private_numbers => _numbers;
public MyNumbers2(IEnumerable<int> __private_numbers = null)
{
_numbers = __private_numbers as List<int> ?? __private_numbers?.ToList() ?? new();
}
}
如果一个外部编码人员愚蠢到像它是该类的正常编程接口一样访问私有数据,那么他就应该受到谴责。您完全有权更改该“私有接口”,而无需感到任何内疚。并且他也不能通过 IEnumerable
搞乱您的内部列表。
在大多数情况下,这应该足够了。
System.Text.Json
默认不支持内部和私有的Getter和Setter。详情请参考:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#internal-and-private-property-setters-and-getters - Pavel Anikhouski