如何在反序列化过程中以编程方式选择构造函数?

时间:2015-01-26 17:14:20

标签: c# json.net

我想按以下方式反序列化序列化的System.Security.Claims.Claim对象:

{
    "Issuer" : "LOCAL AUTHORITY",
    "OriginalIssuer" : "LOCAL AUTHORITY",
    "Type" : "http://my.org/ws/2015/01/identity/claims/mytype",
    "Value" : "myvalue",
    "ValueType" : "http://www.w3.org/2001/XMLSchema#string"
}

我得到的是JsonSerializationException

  

无法找到用于类型的构造函数   System.Security.Claims.Claim。一个类应该有一个默认值   构造函数,带有参数的构造函数或标记的构造函数   使用JsonConstructor属性。

经过一些调查后,我终于理解了上面消息中 one 的含义:JSON反序列化器无法找到正确的构造函数 - 在Claim类型的情况下 - < strong>带参数的多个构造函数(尽管存在一个构造函数,其参数与上述属性完全匹配)。

有没有办法告诉反序列化器在没有将JsonConstructor属性添加到该mscorlib类型的情况下选择哪个构造函数?

Daniel Halan用patch to Json.NET a few years ago解决了这个问题。有没有办法在不修改Json.NET的情况下解决这个问题?

4 个答案:

答案 0 :(得分:31)

如果无法向目标类添加[JsonConstructor]属性(因为您不拥有代码),那么通常的解决方法是创建自定义JsonConverter,如下所示: @James Thorpe在评论中说。这很简单。您可以将JSON加载到JObject,然后从中选择单个属性以实例化您的Claim实例。以下是您需要的代码:

class ClaimConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(System.Security.Claims.Claim));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        string type = (string)jo["Type"];
        string value = (string)jo["Value"];
        string valueType = (string)jo["ValueType"];
        string issuer = (string)jo["Issuer"];
        string originalIssuer = (string)jo["OriginalIssuer"];
        return new Claim(type, value, valueType, issuer, originalIssuer);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

要使用转换器,只需将其实例传递给JsonConvert.DeserializeObject<T>()方法调用:

Claim claim = JsonConvert.DeserializeObject<Claim>(json, new ClaimConverter());

小提琴:https://dotnetfiddle.net/7LjgGR

答案 1 :(得分:10)

另一种方法,至少适用于非密封类,是将它子类化,但只有你感兴趣的构造函数:

class MyClaim : Claim {
    public MyClaim(string type, string value, string valueType, string issuer, string originalIssuer):
        base(type, value, valueType, issuer, originalIssuer){}
}

然后,您可以在没有帮助程序类的情况下反序列化到此对象,然后将其视为基本类型。

Claim claim = JsonConvert.DeserializeObject<MyClaim>(json);

对于密封课程,您可以采用这种方法(假装密封Claim的一秒钟):

class MyClaim {
    private Claim _claim;
    public MyClaim(string type, string value, string valueType, string issuer, string originalIssuer) {
        _claim = new Claim(type, value, valueType, issuer, originalIssuer);
    }
    public Claim Value { get {
            return _claim;
        }
    }
}

Claim claim = JsonConvert.DeserializeObject<MyClaim>(json).Value;

答案 2 :(得分:1)

ClaimConverter已与IdentityServer4打包在一起。

命名空间:IdentityServer4.Stores.Serialization

使用示例:

JsonConvert.DeserializeObject<T>(value, new IdentityServer4.Stores.Serialization.ClaimConverter());

答案 3 :(得分:0)

感谢@ chen-zhe,这是上述帖子(包括可接受的答案)中最简单的解决方案。 就我而言,我需要对包括“索赔”在内的整个“客户”(Is4模型)进行反序列化,这只是“客户”的子类之一。

这是我尝试并成功解决的问题-

[HttpPost]
public async Task<ActionResult<Client>> Post(Object model)
{
   var clientString = model.ToString();
   Client client = JsonConvert.DeserializeObject<Client>(clientString, new ClaimConverter());
}

要使这项工作有效的注意事项:

  • 首先,在我的案例中,您可能想取消默认的框架绑定(ASP.NET Core [FromBody]属性和
  • 将ClaimCoverter解析为IdentityServer4.Stores.Serialization中的本机Id4框架库类,无需重新发明创建上面建议的自定义JsonConverter的方法,甚至无需.NET Core Custom Model绑定方法