JsonConverter,其中属性由其父级上的字段组成 [英] JsonConverter where the property is composed of fields on its parent

查看:69
本文介绍了JsonConverter,其中属性由其父级上的字段组成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到这样的情况,一个API具有多个类似数组的对象作为对象上的各个属性.例如:

I have a situation where an API has multiple array-like objects as individual properties on a object. For example:

"parent": {
    id: 4,
    /*... other fields ...*/
    "prop_1": "A",
    "prop_2": "B",
    /*... other "props" ...*/
    "prop_24": "W"
}

我希望C#中的结果模型不重复相同的结构,而将prop_X反序列化为List并序列化回该烂摊子.

I want the resulting model in C# to not repeat that same structure and have prop_X deserialized as a List and serialized back to that mess.

class Parent {
    [JsonProperty("id")]
    public int ParentId { get; set; }
    /*... other properties ...*/
    public List<string> Props { get; set; }
}

我尝试将JsonConverter属性添加到Props属性,但是我不知道如何在父级上获得所需的道具.我可以将转换器添加到父对象,但是由于两个原因,它导致了问题.

I tried added JsonConverter attribute to the Props property, but I couldn't figure out how to get the props I needed on the parent. I could add a converter to the parent object, but for two reasons it causes a problem.

  1. 大多数字段都映射为简单的属性,我不想编写代码来手动对所有它们进行反序列化和序列化.
  2. "prop_xx"字段的约定出现在多个对象中,我不愿意为每个对象编写JsonConverters.

我的想法是让所有对象实现一个接口IHasProps,并编写一个IHasPropsJsonConverter.转换器将尝试使用尽可能多的内置功能来读取和写入道具,但遇到写入时指示其道具对象的类型的属性以及读取时与模式^prop\d+$匹配的字段时,转换器将尝试使用该功能.

My idea was to have all the objects implement an interface, IHasProps, and write an IHasPropsJsonConverter. The converter would try to use as much as the built in functionality to read and write props, except when encountering an attribute on a type that indicates its a prop object when write, and a field that matches the pattern ^prop\d+$ when reading.

这似乎是 overkill .有更好的方法吗?

This seems like overkill. Is there a better way?

推荐答案

您使用转换器的方法应该可以工作,但是以通用方式进行操作却有些棘手,而不会出现堆栈溢出异常.有关编写的信息,请参见在返回之前修改JSON的通用方法给客户的一种方式.为了阅读,您可以加载到 JObject 中,属性遵循 Json.NET使用JsonConverter进行自定义序列化-如何获得默认"行为,然后识别并解析"prop_XXX"属性.请注意,这些解决方案与 TypeNameHandling PreserveReferencesHandling .

Your approach of using a converter should work but is a little tricky to do in a generic fashion without getting a stack overflow exception. For writing, see Generic method of modifying JSON before being returned to client for one way of doing it. For reading, you can load into a JObject, populate the regular properties along the lines of Json.NET custom serialization with JsonConverter - how to get the "default" behavior then identify and parse the "prop_XXX" properties. Note that these solutions don't play well with TypeNameHandling or PreserveReferencesHandling.

但是,一种更简单的方法可能是使用 [JsonExtensionData] 在序列化过程中将变量的可变属性集临时存储在IDictionary<string, JToken>中,然后在序列化完成后将它们添加到List<string> Props中.可以使用串行化回调:

However, a simpler approach may be to make use of [JsonExtensionData] to temporarily store your variable set of properties in a IDictionary<string, JToken> during the serialization process and then add them to the List<string> Props when serialization is complete. This can be done using serialization callbacks:

public class Parent
{
    public Parent() { this.Props = new List<string>(); }

    [JsonProperty("id")]
    public int ParentId { get; set; }

    [JsonProperty("value")]
    public string Value { get; set; }

    [JsonIgnore]
    public List<string> Props { get; set; }

    [JsonExtensionData]
    JObject extensionData; // JObject implements IDictionary<string, JToken> and preserves document order.

    [OnSerializing]
    void OnSerializing(StreamingContext ctx)
    {
        VariablePropertyListExtensions.OnSerializing(Props, ref extensionData, false);
    }

    [OnSerialized]
    void OnSerialized(StreamingContext ctx)
    {
        VariablePropertyListExtensions.OnSerialized(Props, ref extensionData, false);
    }

    [OnDeserializing]
    void OnDeserializing(StreamingContext ctx)
    {
        VariablePropertyListExtensions.OnDeserializing(Props, ref extensionData, false);
    }

    [OnDeserialized]
    void OnDeserialized(StreamingContext ctx)
    {
        if (Props == null)
            Props = new List<string>();
        VariablePropertyListExtensions.OnDeserialized(Props, ref extensionData, false);
    }
}

public static class VariablePropertyListExtensions
{
    public const string Prefix = "prop_";

    readonly static Regex regex;

    static VariablePropertyListExtensions()
    {
        regex = new Regex("^" + Prefix + @"\d+" + "$", RegexOptions.CultureInvariant | RegexOptions.Compiled); // Add  | RegexOptions.IgnoreCase if required
    }

    public static void OnSerializing<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
    {
        Debug.Assert(keepUnknownProperties || (extensionData == null || extensionData.Count == 0));

        // Add the prop_ properties.
        if (props == null || props.Count < 1)
            return;
        extensionData = extensionData ?? new TDictionary();
        for (int i = 0; i < props.Count; i++)
            extensionData.Add(Prefix + (i + 1).ToString(NumberFormatInfo.InvariantInfo), (JValue)props[i]);
    }

    internal static void OnSerialized<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
    {
        // Remove the prop_ properties.
        if (extensionData == null)
            return;
        foreach (var name in extensionData.Keys.Where(k => regex.IsMatch(k)).ToList())
            extensionData.Remove(name);
        // null out extension data if no longer needed
        if (!keepUnknownProperties || extensionData.Count == 0)
            extensionData = null;
    }

    internal static void OnDeserializing<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
    {
        Debug.Assert(keepUnknownProperties || (extensionData == null || extensionData.Count == 0));
    }

    internal static void OnDeserialized<TDictionary>(IList<string> props, ref TDictionary extensionData, bool keepUnknownProperties) where TDictionary : class, IDictionary<string, JToken>, new()
    {
        props.Clear();
        if (extensionData == null)
            return;
        foreach (var item in extensionData.Where(i => regex.IsMatch(i.Key)).ToList())
        {
            props.Add(item.Value.ToObject<string>());
            extensionData.Remove(item.Key);
        }
        // null out extension data if no longer needed
        if (!keepUnknownProperties || extensionData.Count == 0)
            extensionData = null;
    }
}

在这里,我已经将用于填充和反序列化扩展数据字典的逻辑移到了一个帮助程序类中,以便在多个类中重用.请注意,我正在按文档顺序将"prop_XXX"属性添加到属性列表中.由于标准指出JSON对象是键/值对的无序集,为增强鲁棒性,您可能希望按它们的XXX索引对其进行排序.

Here I have moved the logic for populating and deserializing the extension data dictionary to a helper class for reuse in multiple classes. Note that I am adding the "prop_XXX" properties to the properties list in document order. Since the standard states that a JSON object is an unordered set of key/value pairs, for added robustness you might want to sort them by their XXX index.

示例小提琴.

这篇关于JsonConverter,其中属性由其父级上的字段组成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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