是否可以使用属性扩展 JsonProperty 属性 class 以影响不同的序列化?
Is it possible to extend the JsonProperty attribute class with properties to affect different serializations?
我的问题类似于这个问题:How to ignore JsonProperty(PropertyName = "someName") when serializing json? 其中的解决方案适用于我,但我很想知道是否可以使用更多属性扩展 [JsonProperty]
属性(使用 Newtonsoft.json)?
一些背景:
我有一个应用程序(我们称它为 SmartModel),它根据用户输入在 c# 中生成一个物理模型。 SmartModel 由许多具有许多属性的 class 组成(例如 Pipe
class 具有 Length
、Diameter
等属性)。 SmartModel 写出一个 json 类型的 DTO,用于单独的应用程序以解决问题(我们称它为 Solver 和 DTO,SolverDTO)。然而,除了这个 SmartModel 还写出一个不同的 DTO 用于保存和打开目的(方便地称为 SmartModelDTO)。
在这方面,在SmartModel中的某些属性(例如[JsonProperty(SolverPropertyName = "someName")]
)之上有一个装饰器,然后设置一个合同解析器来序列化并写出(以json格式)会很方便):
- 生成SolverDTO时的
SolverPropertyName
和
- 生成SmartModelDTO时
UnderlyingName
(其中 UnderlyingName
默认情况下已经是 JsonProperty
的 属性 并且 SolverPropertyName
应该扩展 JsonProperty
的 属性与).
编辑
这里有一个最小的代码示例来解释我正在努力实现的目标:
我有一个名为 Pipe
的示例 class,如下所示:
class Pipe
{
[JsonProperty(PropertyName = "z_axis_dimension")]
public double Length { get; set; }
[JsonProperty(PropertyName = "cross_sectional_dimension")]
public double Diameter { get; set; }
}
我想以两种不同的方式进行序列化(将来可能更多)。对于 SmartModelDTO,我希望序列化程序使用 UnderlyingProperty
,但对于 SolverDTO,我希望在 JsonProperty 属性中使用 PropertyName
。为此,可以实施以下合同解析器:
class IgnoreJsonPropertyNameContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(
Type type,
MemberSerialization memberSerialization)
{
IList<JsonProperty> list = base.CreateProperties(
type,
memberSerialization);
foreach (JsonProperty prop in list)
{
prop.PropertyName = prop.UnderlyingName;
}
return list;
}
}
使用示例如下:
// Instance of Pipe:
Pipe pipe = new()
{
Length = 10.2,
Diameter = 5.5,
};
// Set some Json serializer settings:
JsonSerializerSettings jsonSerializerSettings = new();
jsonSerializerSettings.Formatting = Formatting.Indented;
// Serialize Pipe where property values are obtained from the
// JsonProperty PropertyName:
string solverJsonString = JsonConvert.SerializeObject(
pipe, jsonSerializerSettings);
Console.WriteLine($"Serialized string for SolverDTO:");
Console.WriteLine($"{solverJsonString}");
Console.WriteLine();
// Set a new contract resolver to return the
// JsonProperty UnderlyingName instead of the PropertyName:
jsonSerializerSettings.ContractResolver =
new IgnoreJsonPropertyNameContractResolver();
// Serialize Pipe where property values are obtained from the
// JsonProperty UnderlyingName:
string smartModelJsonString = JsonConvert.SerializeObject(
pipe, jsonSerializerSettings);
Console.WriteLine($"Serialized string for SmartModelDTO:");
Console.WriteLine($"{smartModelJsonString}");
Console.ReadLine();
给出以下输出:
Serialized string for SolverDTO:
{
"z_axis_dimension": 10.2,
"cross_sectional_dimension": 5.5
}
Serialized string for SmartModelDTO:
{
"Length": 10.2,
"Diameter": 5.5
}
但是,我希望在 Pipe
中有一个功能来标记属性,例如:
class Pipe
{
[JsonProperty(
SolverPropertyName = "z_axis_dimension",
APIPropertyName = "distance")]
public double Length { get; set; }
[JsonProperty(
SolverPropertyName = "cross_sectional_dimension",
APIPropertyName = "diameter")]
public double Diameter { get; set; }
}
然后设置与上述类似的不同合同解析器来序列化 Pipe
对象,但一个序列化使用 SolverPropertyName
,另一个序列化 APIPropertyName
,等等...
可以扩展 [JsonProperty]
class 例如除了 PropertyName
?
之外,还具有 SolverPropertyName
、APIPropertyName
等属性
由于缺少代码和示例(在撰写本文时),我不确定我是否完全符合您的要求。但这将用于从同一基础生成不同的序列化输出。
定义一个包含所有属性的接口
public interface IBase
{
string PropertyOne { get; set; }
string PropertyTwo { get; set; }
string PropertyThree { get; set; }
string PropertyFour { get; set; }
}
然后你的第一个 class 被序列化,然后使用我们定义的接口。
这里我们使用 JsonProperty
属性来定义我们想要的输出名称,这与属性的实际名称不同。
我们还使用 NewtonSoft 的 ShouldSerialize
功能来抑制输出第四个 属性,这在本例中是我们不想要的。
public class First: IBase
{
[JsonProperty("PropertyNumber1")]
public string PropertyOne { get; set; }
[JsonProperty("PropertyNumber2")]
public string PropertyTwo { get; set; }
[JsonProperty("PropertyNumber3")]
public string PropertyThree { get; set; }
public string PropertyFour { get; set; }
public bool ShouldSerializePropertyFour() { return false; }
}
然后我们类似地定义我们的另一个class,但是这里我们抑制第一个属性,并通过JsonProperty
给其余的一个不同的名字
public class Second : IBase
{
[JsonProperty("SecondProperty")]
public string PropertyTwo { get; set; }
[JsonProperty("ThirdProperty")]
public string PropertyThree { get; set; }
[JsonProperty("FourthProperty")]
public string PropertyFour { get; set; }
public string PropertyOne { get; set; }
public bool ShouldSerializePropertyOne() { return false; }
}
然后当我们序列化 classes 时,我们只得到我们想要的属性,而且它们有不同的名称。
除了使用接口,你还可以使用抽象 class 来做同样的事情,但在这里你需要在抽象 class 中将属性定义为 virtual
并在中覆盖它们child classes。请注意,为了使 ShouldSerialize
起作用,您需要先覆盖它,然后再抑制它!
我会将其添加为单独的答案。我认为我提供的另一个答案对您仍然有效,并且代码更简洁易读。
下面的这个方法可行,但它(恕我直言)比较混乱(可能会改进)并且可能导致更多错误。
Newtonsoft 的 JsonProperty
属性是密封的,因此我们无法覆盖它。这意味着我们首先需要创建自己的属性。
在这里我们定义了两个属性,它们对应于您要使用的名称。
[AttributeUsage(AttributeTargets.Property)]
public class MyJsonPropertyAttribute : Attribute
{
public string SolverPropertyName { get; set; }
public string APIPropertyName { get; set; }
}
然后你可以这样装饰你的class
public class Pipe
{
[MyJsonProperty(
SolverPropertyName = "z_axis_dimension",
APIPropertyName = "distance")]
public double Length { get; set; }
[MyJsonProperty(
SolverPropertyName = "cross_sectional_dimension",
APIPropertyName = "diameter")]
public double Diameter { get; set; }
}
接下来我们需要定义我们将调用的自定义合同解析器 DualNameContractResolver
。在构造过程中,我们需要指定要使用的属性名称 属性。您也可以将其更改为枚举(这样可以减少错误)。
如果您指定了错误的名称,则将应用 Newtonsoft 的默认序列化。
public class DualNameContractResolver : DefaultContractResolver
{
private readonly string _propertyDecoratorToUse;
public DualNameContractResolver(string propertyDecoratorToUse)
{
_propertyDecoratorToUse = propertyDecoratorToUse ?? throw new ArgumentNullException(nameof(propertyDecoratorToUse));
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
CustomAttributeNamedArgument namedArgument = member.CustomAttributes.SelectMany(c => c.NamedArguments).Where(w => w.MemberName.Equals(_propertyDecoratorToUse)).FirstOrDefault();
if(namedArgument.TypedValue.Value != null)
property.PropertyName = namedArgument.TypedValue.Value.ToString();
return property;
}
}
如您所见,在 CreateProperty
方法中,我们查找请求的 属性,如果找到则应用定义的值(即序列化名称)。
然后您可以像这样使用所有这些。
Pipe pipe = new()
{
Length = 10.2,
Diameter = 5.5,
};
var solverResolver = new DualNameContractResolver("SolverPropertyName");
var apiResolver = new DualNameContractResolver("APIPropertyName");
JsonSerializerSettings jsonSerializerSettings = new();
jsonSerializerSettings.Formatting = Formatting.Indented;
jsonSerializerSettings.ContractResolver = solverResolver;
string json1 = JsonConvert.SerializeObject(pipe, jsonSerializerSettings);
jsonSerializerSettings.ContractResolver = apiResolver;
string json2 = JsonConvert.SerializeObject(pipe, jsonSerializerSettings);
我的问题类似于这个问题:How to ignore JsonProperty(PropertyName = "someName") when serializing json? 其中的解决方案适用于我,但我很想知道是否可以使用更多属性扩展 [JsonProperty]
属性(使用 Newtonsoft.json)?
一些背景:
我有一个应用程序(我们称它为 SmartModel),它根据用户输入在 c# 中生成一个物理模型。 SmartModel 由许多具有许多属性的 class 组成(例如 Pipe
class 具有 Length
、Diameter
等属性)。 SmartModel 写出一个 json 类型的 DTO,用于单独的应用程序以解决问题(我们称它为 Solver 和 DTO,SolverDTO)。然而,除了这个 SmartModel 还写出一个不同的 DTO 用于保存和打开目的(方便地称为 SmartModelDTO)。
在这方面,在SmartModel中的某些属性(例如[JsonProperty(SolverPropertyName = "someName")]
)之上有一个装饰器,然后设置一个合同解析器来序列化并写出(以json格式)会很方便):
- 生成SolverDTO时的
SolverPropertyName
和 - 生成SmartModelDTO时
UnderlyingName
(其中 UnderlyingName
默认情况下已经是 JsonProperty
的 属性 并且 SolverPropertyName
应该扩展 JsonProperty
的 属性与).
编辑
这里有一个最小的代码示例来解释我正在努力实现的目标:
我有一个名为 Pipe
的示例 class,如下所示:
class Pipe
{
[JsonProperty(PropertyName = "z_axis_dimension")]
public double Length { get; set; }
[JsonProperty(PropertyName = "cross_sectional_dimension")]
public double Diameter { get; set; }
}
我想以两种不同的方式进行序列化(将来可能更多)。对于 SmartModelDTO,我希望序列化程序使用 UnderlyingProperty
,但对于 SolverDTO,我希望在 JsonProperty 属性中使用 PropertyName
。为此,可以实施以下合同解析器:
class IgnoreJsonPropertyNameContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(
Type type,
MemberSerialization memberSerialization)
{
IList<JsonProperty> list = base.CreateProperties(
type,
memberSerialization);
foreach (JsonProperty prop in list)
{
prop.PropertyName = prop.UnderlyingName;
}
return list;
}
}
使用示例如下:
// Instance of Pipe:
Pipe pipe = new()
{
Length = 10.2,
Diameter = 5.5,
};
// Set some Json serializer settings:
JsonSerializerSettings jsonSerializerSettings = new();
jsonSerializerSettings.Formatting = Formatting.Indented;
// Serialize Pipe where property values are obtained from the
// JsonProperty PropertyName:
string solverJsonString = JsonConvert.SerializeObject(
pipe, jsonSerializerSettings);
Console.WriteLine($"Serialized string for SolverDTO:");
Console.WriteLine($"{solverJsonString}");
Console.WriteLine();
// Set a new contract resolver to return the
// JsonProperty UnderlyingName instead of the PropertyName:
jsonSerializerSettings.ContractResolver =
new IgnoreJsonPropertyNameContractResolver();
// Serialize Pipe where property values are obtained from the
// JsonProperty UnderlyingName:
string smartModelJsonString = JsonConvert.SerializeObject(
pipe, jsonSerializerSettings);
Console.WriteLine($"Serialized string for SmartModelDTO:");
Console.WriteLine($"{smartModelJsonString}");
Console.ReadLine();
给出以下输出:
Serialized string for SolverDTO:
{
"z_axis_dimension": 10.2,
"cross_sectional_dimension": 5.5
}
Serialized string for SmartModelDTO:
{
"Length": 10.2,
"Diameter": 5.5
}
但是,我希望在 Pipe
中有一个功能来标记属性,例如:
class Pipe
{
[JsonProperty(
SolverPropertyName = "z_axis_dimension",
APIPropertyName = "distance")]
public double Length { get; set; }
[JsonProperty(
SolverPropertyName = "cross_sectional_dimension",
APIPropertyName = "diameter")]
public double Diameter { get; set; }
}
然后设置与上述类似的不同合同解析器来序列化 Pipe
对象,但一个序列化使用 SolverPropertyName
,另一个序列化 APIPropertyName
,等等...
可以扩展 [JsonProperty]
class 例如除了 PropertyName
?
SolverPropertyName
、APIPropertyName
等属性
由于缺少代码和示例(在撰写本文时),我不确定我是否完全符合您的要求。但这将用于从同一基础生成不同的序列化输出。
定义一个包含所有属性的接口
public interface IBase
{
string PropertyOne { get; set; }
string PropertyTwo { get; set; }
string PropertyThree { get; set; }
string PropertyFour { get; set; }
}
然后你的第一个 class 被序列化,然后使用我们定义的接口。
这里我们使用 JsonProperty
属性来定义我们想要的输出名称,这与属性的实际名称不同。
我们还使用 NewtonSoft 的 ShouldSerialize
功能来抑制输出第四个 属性,这在本例中是我们不想要的。
public class First: IBase
{
[JsonProperty("PropertyNumber1")]
public string PropertyOne { get; set; }
[JsonProperty("PropertyNumber2")]
public string PropertyTwo { get; set; }
[JsonProperty("PropertyNumber3")]
public string PropertyThree { get; set; }
public string PropertyFour { get; set; }
public bool ShouldSerializePropertyFour() { return false; }
}
然后我们类似地定义我们的另一个class,但是这里我们抑制第一个属性,并通过JsonProperty
public class Second : IBase
{
[JsonProperty("SecondProperty")]
public string PropertyTwo { get; set; }
[JsonProperty("ThirdProperty")]
public string PropertyThree { get; set; }
[JsonProperty("FourthProperty")]
public string PropertyFour { get; set; }
public string PropertyOne { get; set; }
public bool ShouldSerializePropertyOne() { return false; }
}
然后当我们序列化 classes 时,我们只得到我们想要的属性,而且它们有不同的名称。
除了使用接口,你还可以使用抽象 class 来做同样的事情,但在这里你需要在抽象 class 中将属性定义为 virtual
并在中覆盖它们child classes。请注意,为了使 ShouldSerialize
起作用,您需要先覆盖它,然后再抑制它!
我会将其添加为单独的答案。我认为我提供的另一个答案对您仍然有效,并且代码更简洁易读。
下面的这个方法可行,但它(恕我直言)比较混乱(可能会改进)并且可能导致更多错误。
Newtonsoft 的 JsonProperty
属性是密封的,因此我们无法覆盖它。这意味着我们首先需要创建自己的属性。
在这里我们定义了两个属性,它们对应于您要使用的名称。
[AttributeUsage(AttributeTargets.Property)]
public class MyJsonPropertyAttribute : Attribute
{
public string SolverPropertyName { get; set; }
public string APIPropertyName { get; set; }
}
然后你可以这样装饰你的class
public class Pipe
{
[MyJsonProperty(
SolverPropertyName = "z_axis_dimension",
APIPropertyName = "distance")]
public double Length { get; set; }
[MyJsonProperty(
SolverPropertyName = "cross_sectional_dimension",
APIPropertyName = "diameter")]
public double Diameter { get; set; }
}
接下来我们需要定义我们将调用的自定义合同解析器 DualNameContractResolver
。在构造过程中,我们需要指定要使用的属性名称 属性。您也可以将其更改为枚举(这样可以减少错误)。
如果您指定了错误的名称,则将应用 Newtonsoft 的默认序列化。
public class DualNameContractResolver : DefaultContractResolver
{
private readonly string _propertyDecoratorToUse;
public DualNameContractResolver(string propertyDecoratorToUse)
{
_propertyDecoratorToUse = propertyDecoratorToUse ?? throw new ArgumentNullException(nameof(propertyDecoratorToUse));
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
CustomAttributeNamedArgument namedArgument = member.CustomAttributes.SelectMany(c => c.NamedArguments).Where(w => w.MemberName.Equals(_propertyDecoratorToUse)).FirstOrDefault();
if(namedArgument.TypedValue.Value != null)
property.PropertyName = namedArgument.TypedValue.Value.ToString();
return property;
}
}
如您所见,在 CreateProperty
方法中,我们查找请求的 属性,如果找到则应用定义的值(即序列化名称)。
然后您可以像这样使用所有这些。
Pipe pipe = new()
{
Length = 10.2,
Diameter = 5.5,
};
var solverResolver = new DualNameContractResolver("SolverPropertyName");
var apiResolver = new DualNameContractResolver("APIPropertyName");
JsonSerializerSettings jsonSerializerSettings = new();
jsonSerializerSettings.Formatting = Formatting.Indented;
jsonSerializerSettings.ContractResolver = solverResolver;
string json1 = JsonConvert.SerializeObject(pipe, jsonSerializerSettings);
jsonSerializerSettings.ContractResolver = apiResolver;
string json2 = JsonConvert.SerializeObject(pipe, jsonSerializerSettings);