使用自定义JsonConverter和自定义ContractResolver时出现StackOverflow异常 [英] StackOverflow exception when using custom JsonConverter and custom ContractResolver

查看:75
本文介绍了使用自定义JsonConverter和自定义ContractResolver时出现StackOverflow异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的要求是在反序列化期间使用JsonProperty,并在序列化期间忽略JsonProperty.我的模特,

My requirement is use JsonProperty during de-serializing and ignore JsonProperty during serialization. My model,

[JsonConverter(typeof(JsonPathConverter))]
public class FacebookFeed
{
    public FacebookFeed()
    {
        Posts = new List<FacebookFeedPost>();
    }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("fan_count")]
    public int Likes { get; set; }

    [JsonProperty("feed.data")]
    public List<FacebookFeedPost> Posts { get; set; }
}

public class FacebookFeedPost
{
    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("message")]
    public string Message { get; set; }

    [JsonProperty("created_time")]
    public DateTime Date { get; set; }

    [JsonProperty("comments.summary.total_count")]
    public int Comments { get; set; }        
}

class IgnoreJsonPropertyContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var list = base.CreateProperties(type, memberSerialization);
        foreach (JsonProperty prop in list)
        {
            prop.PropertyName = prop.UnderlyingName;
        }
        return list;
    }
}

public class JsonPathConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType,
                                    object existingValue, JsonSerializer serializer)
    {
        var jo = JObject.Load(reader);
        object targetObj = Activator.CreateInstance(objectType);

        foreach (var prop in objectType.GetProperties()
                                                .Where(p => p.CanRead && p.CanWrite))
        {
            var att = prop.GetCustomAttributes(true).OfType<JsonPropertyAttribute>().FirstOrDefault();
            var jsonPath = (att != null ? att.PropertyName : prop.Name);
            var token = jo.SelectToken(jsonPath);
            if (token != null && token.Type != JTokenType.Null)
            {
                var value = token.ToObject(prop.PropertyType, serializer);
                prop.SetValue(targetObj, value, null);
            }
        }

        return targetObj;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var s = new JsonSerializer();
        serializer.ContractResolver = new IgnoreJsonPropertyContractResolver();
        var t = JToken.FromObject(value, s);
        t.WriteTo(writer);
    }

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when [JsonConverter] attribute is used
        return false;
    }

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

 }

问题是WriteJson.请注意,我使用的是ASP.NET Web Api,它在内部对对象进行序列化,这就是为什么我使用JsonConverter的原因.我不想更改全局Web API设置.

The problem is WriteJson. Note that I am using ASP.NET Web Api which internally serialize my objects that's why I am using JsonConverter. I dont wanna change global web api settings.

推荐答案

我认为您有一个

I think you have an XY Problem going on here.

我认为您要解决的真正问题是,您要嵌套一些深度嵌套的JSON,然后将其反序列化为一个更简单,更扁平的模型,然后希望能够将该更简单的模型序列化为JSON.

I think the real problem you are trying to solve is that you have some deeply nested JSON which you want to deserialize into a simpler, flattened model, and then you want to be able to serialize that simpler model to JSON.

对于反序列化部分,您发现了一种可能的解决方案,该转换器重载了[JsonProperty]属性以接受每个属性的路径,从而更容易展平为更简单的模型.您已使用[JsonConverter]属性将转换器应用于类,因为您不想修改Web API的全局设置.

You've found a possible solution for the deserialization part, a converter which overloads the [JsonProperty] attribute to accept a path for each property, making it easier to flatten to a simpler model. You've used a [JsonConverter] attribute to apply the converter to your class because you don't want to modify the global settings for Web API.

但是现在,当您进行序列化时,不需要的序列化程序会获取[JsonProperty]名称(路径).因此,您需要一种忽略它们的方法.然后,您找到了一种可能的解决方案,其中涉及使用自定义合同解析器将[JsonProperty]名称恢复为原始名称价值观.您试图在转换器的WriteJson方法中应用解析器,但是当您尝试在转换器内部序列化对象时,由于模型类具有[JsonConverter]属性,因此递归调用了转换器.因此,现在您陷入困境,并且正在询问如何解决这个最新问题.我到目前为止正确吗?

But now, when you serialize, the [JsonProperty] names (paths) are getting picked up by the serializer, which you don't want. So you needed a way to ignore them. You then found a possible solution for that, involving using a custom contract resolver to revert the [JsonProperty] names back to their original values. You are trying to apply the resolver inside the converter's WriteJson method, but when you try to serialize your object inside the converter, the converter gets called recursively due to the fact that the model class has a [JsonConverter] attribute on it. So now you're stuck, and you're asking how to get around this latest problem. Am I right so far?

好的,让我们备份一些步骤并解决实际问题.您想将深度嵌套的JSON反序列化为一个简单模型,然后将该简单模型序列化为JSON.我认为您使用转换器将反序列化的JSON扁平化变得正确,但是没有理由转换器必须劫持[JsonProperty]属性来执行此操作.相反,它可以将自己的自定义属性用于JSON路径,以免干扰序列化程序的正常操作.如果这样做,则可以使转换器的CanWrite方法返回false,这将导致序列化程序忽略转换器,并使用默认属性名称,这是您首先要使用的名称.

OK, so let's back up a few steps and solve the real problem. You want to deserialize a deeply nested JSON into a simple model and then serialize that simple model to JSON. I think you are on the right track using a converter to flatten the JSON down on deserialization, but there's no reason that the converter has to hijack the [JsonProperty] attribute to do it. It could instead use its own custom attribute for the JSON paths so as not to interfere with the normal operation of the serializer. If you do that, then you can make the converter's CanWrite method return false, which will cause the serializer to ignore the converter and use the default property names, which is what you wanted in the first place.

所以,这是您需要做的:

So, here is what you need to do:

首先,创建一个用于属性路径的自定义属性类:

First, make a custom attribute class to use for the property paths:

public class JsonPathAttribute : Attribute
{
    public JsonPathAttribute(string jsonPath)
    {
        JsonPath = jsonPath;
    }

    public string JsonPath { get; set; }
}

接下来,更改转换器的ReadJson方法以查找此新属性而不是[JsonProperty],并使CanWrite方法返回false.您也可以摆脱WriteJson方法的实现,因为它将永远不会被调用.自定义解析器类也不需要.

Next, change the ReadJson method of your converter to look for this new attribute instead of [JsonProperty], and make the CanWrite method return false. You can also get rid of the implementation for the WriteJson method, as it will never be called. The custom resolver class is not needed either.

public class JsonPathConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType,
                                    object existingValue, JsonSerializer serializer)
    {
        var jo = JObject.Load(reader);
        object targetObj = Activator.CreateInstance(objectType);

        foreach (var prop in objectType.GetProperties()
                                       .Where(p => p.CanRead && p.CanWrite))
        {
            var att = prop.GetCustomAttributes(true)
                          .OfType<JsonPathAttribute>()
                          .FirstOrDefault();
            var jsonPath = (att != null ? att.JsonPath : prop.Name);
            var token = jo.SelectToken(jsonPath);
            if (token != null && token.Type != JTokenType.Null)
            {
                var value = token.ToObject(prop.PropertyType, serializer);
                prop.SetValue(targetObj, value, null);
            }
        }

        return targetObj;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // WriteJson is not called when CanWrite returns false
    }

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

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when [JsonConverter] attribute is used
        return false;
    }
}

最后,将模型类更改为使用新属性.注意,您还需要使用[JsonConverter]属性标记这两个类.在您的问题中,您仅标记了第一个.

Finally, change your model classes to use the new attribute. Note that you will also need to mark both classes with the [JsonConverter] attribute; in your question you only marked the first one.

[JsonConverter(typeof(JsonPathConverter))]
public class FacebookFeed
{
    public FacebookFeed()
    {
        Posts = new List<FacebookFeedPost>();
    }

    [JsonPath("name")]
    public string Name { get; set; }

    [JsonPath("fan_count")]
    public int Likes { get; set; }

    [JsonPath("feed.data")]
    public List<FacebookFeedPost> Posts { get; set; }
}

[JsonConverter(typeof(JsonPathConverter))]
public class FacebookFeedPost
{
    [JsonPath("id")]
    public string Id { get; set; }

    [JsonPath("message")]
    public string Message { get; set; }

    [JsonPath("created_time")]
    public DateTime Date { get; set; }

    [JsonPath("comments.summary.total_count")]
    public int Comments { get; set; }
}

就是这样.现在,它应该可以按照您想要的方式工作了.

And that's it. It should now work the way you want.

演示小提琴: https://dotnetfiddle.net/LPPAmH

这篇关于使用自定义JsonConverter和自定义ContractResolver时出现StackOverflow异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆