简体   繁体   English

JsonConverter,其中属性由其父级上的字段组成

[英]JsonConverter where the property is composed of fields on its parent

I have a situation where an API has multiple array-like objects as individual properties on a object. 我遇到的情况是,API具有多个类似数组的对象作为对象上的各个属性。 For example: 例如:

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

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. 我希望C#中的结果模型不重复相同的结构,并且将prop_X反序列化为List并序列化回该混乱状态。

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

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. 我尝试将JsonConverter属性添加到Props属性,但是我不知道如何在父级上获取所需的道具。 I could add a converter to the parent object, but for two reasons it causes a problem. 我可以将转换器添加到父对象,但是由于两个原因,它导致了问题。

  1. Most of the fields map to simple properties and I don't want to have to write the code to manually deserialize and serialize all of them. 大多数字段都映射为简单的属性,我不需要编写代码来手动对所有它们进行反序列化和序列化。
  2. The convention of "prop_xx" fields appears in multiple objects and I'd hate to write JsonConverters for each object. “ prop_xx”字段的约定出现在多个对象中,我不愿意为每个对象编写JsonConverters。

My idea was to have all the objects implement an interface, IHasProps , and write an IHasPropsJsonConverter . 我的想法是让所有对象实现一个接口IHasProps并编写一个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. 转换器将尝试使用尽可能多的内置功能来读取和写入道具,除非遇到类型的属性,该属性在写入时指示其道具对象,并且在读取时遇到与模式^prop\\d+$匹配的字段。

This seems like overkill . 这似乎有点过分了 Is there a better way? 有没有更好的办法?

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. 有关编写方法 ,请参阅修改JSON的通用方法,然后将其返回给客户端了解一种实现方法。 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. 为了阅读,您可以加载到JObject ,使用JObject沿着Json.NET自定义序列化的行填充常规属性-如何获得“默认”行为,然后识别并解析"prop_XXX"属性。 Note that these solutions don't play well with TypeNameHandling or PreserveReferencesHandling . 请注意,这些解决方案不能与TypeNameHandlingPreserveReferencesHandling

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. 但是,更简单的方法可能是在序列化过程中利用[JsonExtensionData]将属性的变量集临时存储在IDictionary<string, JToken>中,然后在序列化完成后将它们添加到List<string> Props 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;
    }
}

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. 请注意,我正在按文档顺序将"prop_XXX"属性添加到属性列表中。 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. 由于该标准指出JSON对象是键/值对的无序集合,因此,为了增强鲁棒性,您可能希望按其XXX索引对其进行排序。

Sample fiddle . 样品提琴

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM