使用Json.Net进行序列化和反序列化时,应该使用不同的名称。

9

我从Web API接收到的JSON数据看起来像这样:

[
  {
    "id": 1
    "error_message": "An error has occurred!"
  }
]

我将这些数据反序列化为以下类型的对象:
public class ErrorDetails
{
    public int Id { get; set; }

    [JsonProperty("error_message")]
    public string ErrorMessage { get; set; }
}

在我的应用程序中,我希望再次将ErrorDetails对象序列化为JSON,但是使用属性名ErrorMessage而不是error_message。因此,结果将如下所示:

[
  {
    "Id": 1
    "ErrorMessage": "An error has occurred!"
  }
]

有没有一种简单的方法可以使用Json.Net实现这个目标?或许可以使用自定义解析器和一些类似这样的属性:

public class ErrorDetails
{
    public int Id { get; set; }

    [SerializeAs("ErrorMessage")]
    [DeserializeAs("error_message")]
    public string ErrorMessage { get; set; }
}

但是解析器在我序列化或反序列化时并没有告诉我。


你需要两个类。每个类都是用于生成或读取JSON的合同。 - Sir Rufo
你可以在反序列化JSON时使用SnakeCaseNamingStrategy。请参见此处以了解如何使用它的示例。 - dbc
这看起来非常有趣。请将其添加为答案。 - Aetherix
3个回答

17

您可以利用JsonSerializerSettings、ContractResolver和NamingStrategy。

public class ErrorDetails
{
    public int Id { get; set; }
    public string ErrorMessage { get; set; }
}

var json = "{'Id': 1,'error_message': 'An error has occurred!'}";

你可以使用SnakeCaseNamingStrategy进行反序列化。

var dezerializerSettings = new JsonSerializerSettings
{
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    }
};
var obj = JsonConvert.DeserializeObject<ErrorDetails>(json, dezerializerSettings);
为了再次序列化对象,您无需更改JsonSerializerSettings,因为默认设置将使用属性名称。
var jsonNew = JsonConvert.SerializeObject(obj);

jsonNew = "{'Id': 1,'ErrorMessage': '发生错误了!'}"


或者您可以创建一个合同解析器,它可以决定使用哪个名称。 然后,您可以在反序列化和序列化时决定是否要使用带有下划线的帕斯卡命名格式或名称。

public class CustomContractResolver : DefaultContractResolver
{
    public bool UseJsonPropertyName { get; }

    public CustomContractResolver(bool useJsonPropertyName)
    {
        UseJsonPropertyName = useJsonPropertyName;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (!UseJsonPropertyName)
            property.PropertyName = property.UnderlyingName;

        return property;
    }
}

public class ErrorDetails
{
    public int Id { get; set; }
    [JsonProperty("error_message")]
    public string ErrorMessage { get; set; }
}


var json = "{'Id': 1,'error_message': 'An error has occurred!'}";
var serializerSettings = new JsonSerializerSettings()
{
    ContractResolver = new CustomContractResolver(false)
};
var dezerializerSettings = new JsonSerializerSettings
{
    ContractResolver = new CustomContractResolver(true)
};

var obj = JsonConvert.DeserializeObject<ErrorDetails>(json, dezerializerSettings);
var jsonNew = JsonConvert.SerializeObject(obj, serializerSettings);

jsonNew = "{'Id': 1,'ErrorMessage': '发生了一个错误!'}"


这里要指出的一件事是,如果你决定使用CamelCasePropertyNamesContractResolver作为基础而不是DefaultContractResolver,那么生成的属性名称不会是驼峰命名法。因此,你需要手动将名称转换成驼峰格式,例如:property.UnderlyingName.ToCamleCase()。我们无法直接使用JSON.Net的ToCamelCase()方法,因为它在一个内部的StringUtils类中,所以你必须提供自己的实现:https://github.com/JamesNK/Newtonsoft.Json/blob/666d9760719e5ec5b2a50046f7dbd6a1267c01c6/Src/Newtonsoft.Json/Utilities/StringUtils.cs#L155 - lee_mcmullen

2
另一种在序列化和反序列化时实现不同属性名称的方法是使用 ShouldSerialize 方法:https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm#ShouldSerialize 文档说明如下:
为了有条件地序列化属性,添加一个方法并将其命名与属性相同,然后在方法名前缀中添加 ShouldSerialize。方法的结果决定是否序列化属性。如果该方法返回 true,则属性将被序列化;如果该方法返回 false,则该属性将被跳过。
例如:
public class ErrorDetails
{
    public int Id { get; set; }

    // This will deserialise the `error_message` property from the incoming json into the `GetErrorMessage` property
    [JsonProperty("error_message")]
    public string GetErrorMessage { get; set; }

    // If this method returns false then the property after the `ShouldSerialize` prefix will not be serialised into the output
    public bool ShouldSerializeGetErrorMessage() => false;

    // The serialised output will return `ErrorMessage` with the value from `GetErrorMessage` i.e. `error_message` in the original json
    public string ErrorMessage { get { return GetErrorMessage; } }
}

这会导致稍微增加一些开销,因此如果处理大量属性或数据时要小心,但对于小负载而言,如果您不介意在DTO类中略作修改,那么这可能比编写自定义契约解析器等更快的解决方案。

2

我喜欢@lee_mcmullen的回答,并在我的代码中实现了它。现在我认为我找到了一个稍微更整洁的版本。

public class ErrorDetails
{
    public int Id { get; set; }

    // This will deserialise the `error_message` property from the incoming json and store it in the new `GetErrorMessage` property
    [JsonProperty("error_message")]
    public string GetErrorMessage { get { return ErrorMessage; } set { ErrorMessage = value; } }

    // If this method returns false then the property after the `ShouldSerialize` prefix will not be serialised into the output
    public bool ShouldSerializeGetErrorMessage() => false;

    // The serialised output will return `ErrorMessage` with the value set from `GetErrorMessage` i.e. `error_message` in the original json
    public string ErrorMessage { get; set; }
}

我喜欢这种方式的原因是,在更复杂的模型中,它允许继承,同时将所有“旧”的自定义内容分开。
public class ErrorDetails
{
    public int Id { get; set; }
    public string ErrorMessage { get; set; }
}

// This is our old ErrorDetails that hopefully we can delete one day 
public class OldErrorDetails : ErrorDetails
{
    // This will deserialise the `error_message` property from the incoming json and store it in the new `GetErrorMessage` property
    [JsonProperty("error_message")]
    public string GetErrorMessage { get { return ErrorMessage; } set { ErrorMessage = value; } }

    // If this method returns false then the property after the `ShouldSerialize` prefix will not be serialised into the output
    public bool ShouldSerializeGetErrorMessage() => false;
}

这是非常好的回答,虽然在代码方面有些开销,但它确实能够生成良好的JSON结果。不错! - Kamil Budziewski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接