【问题标题】:How to write JSON out of order--perhaps multiple writers, if so how?如何乱序编写 JSON——可能是多个编写器,如果是这样,如何编写?
【发布时间】:2014-02-14 12:42:39
【问题描述】:

这是我的场景:

我正在展平和序列化一个对象图。最终结果是我要将每种类型的所有对象收集到该类型的数组中,并为每种类型序列化一个数组。但是我需要在遍历对象图时序列化(遍历规则很复杂,需要反射才能读取属性——因此非常不希望对图进行多次遍历——我想使用 JSON。 Net的遍历,使其属性也可以用来控制遍历)。

假设我有TypeA 对象和TypeB 对象,假设TypeA 对象可以具有TypeB 类型的属性,TypeB 对象可以具有TypeA 类型的属性(不一定是互惠的!)。假设我的对象图如下所示:

TypeA aInst1 = new TypeA {
    Id = 1,
    MyB = new TypeB {
        Id = 101,
        MyA = new TypeA {
             Id = 2,
             MyB = null
        }
    }
}

我想结束的是:

{
    typeAs: [
        {
            id: 1,
            myB: 101
        },
        {
            id: 2,
            myB: null
        }
    ],
    typeBs: [
        {
            id: 101,
            myA: 2
        }
    ]
}

这里的关键是:当我正在序列化 TypeAs(例如,TypeAConverter)时,我会将必要的 TypeB 对象添加到 TypeB HashSet 以确保它们被序列化(简单)——但我需要在通过 TypeB 时执行相反的操作——但如果我以线性方式执行此操作,我将已经序列化 TypeA,并且无法向后跳转以添加新发现的对象。

现在,我已经知道我必须处理很多事情(遍历深度、多次传递、获取对根对象的引用以首先到达 TypeXs HashSet),其中大部分我认为我已经已经想通了。

我不确定该怎么做是如何处理序列化的无序性。到目前为止,我的计划是为每种类型启动多个 JsonWriters,然后使用 WriteRaw 将它们的输出附加到主文档/编写器。但要做到这一点,我必须将自己缩小到JsonTextWriter,因为更抽象的JsonWriter 类似乎没有办法访问序列化的输出。例如,我宁愿不限制我的用户能够使用BsonWriter(如果这甚至是一个实际目标……似乎是这样)。

顺便说一句,我并没有想出这种格式,我只是想以尽可能通用的方式实现它。

编辑

对于一些额外的背景,我声明了一个接口(称为IModel),用户将使用它来“标记”可以由这个序列化库特殊处理的任何类——有一个ModelConverter捕获任何IModel 实例以应用这些特殊规则。我还有一个根对象(Payload 类),用户必须使用它来放入他们的 IModel(并设置一些可选配置)。 Payload 对象跟踪 TypeXs 数组(准确地说是在 Dictionary<Type, ISet<object>> 中),我有一个 public static Dictionary<System.Runtime.Serialization.StreamingContext, Payload> 可以进行备份(使用 serializer.Context 可用的 @ 987654344@),这就是我能够在事后返回并添加这些对象的方式。

【问题讨论】:

    标签: json.net


    【解决方案1】:

    对于它的价值,我使用多个JsonTextWriters(每种类型一个)的计划奏效了,尽管它有点笨拙,我仍然宁愿找到一种不会将我锁定在一个 @987654322 中的方法@ 实现。

    这是我实现的核心……显然整个解决方案涵盖了两个 JsonConverters 和其他几个类,但这是我创建、跟踪和使用每个类型 JsonTextWriters 的地方:

    // Handle Appendices
    /* This is a bit messy, because we may add items of a given type to the
        * set we are currently processing. Not only is this an issue because you
        * can't modify a set while you're enumerating it (hence why we make a
        * copy first), but we need to catch the newly added objects and process
        * them as well. So, we have to keep making passes until we detect that
        * we haven't added any new objects to any of the appendices.
        */
    Dictionary<Type, ISet<object>> 
        processed = new Dictionary<Type,ISet<object>>(), 
        toBeProcessed = new Dictionary<Type,ISet<object>>(); // is this actually necessary?
    /* On top of that, we need a new JsonWriter for each appendix--because we
        * may write objects of type A, then while processing type B find that
        * we need to write more objects of type A! So we can't keep appending
        * to the same writer.
        */
    /* Oh, and we have to keep a reference to the TextWriter of the JsonWriter
        * because there's no member to get it back out again. ?!?
        * */
    Dictionary<Type, KeyValuePair<JsonWriter,StringWriter>> writers = new Dictionary<Type,KeyValuePair<JsonWriter,StringWriter>>();
    
    int numAdditions;
    do
    {
        numAdditions = 0;
        foreach (KeyValuePair<Type, ISet<object>> apair in payload.Appendices)
        {
            Type type = apair.Key;
            ISet<object> appendix = apair.Value;
            JsonWriter jw;
            if (writers.ContainsKey(type))
            {
                jw = writers[type].Key;
            }
            else
            {
                // Setup and start the writer for this type...
                StringWriter sw = new StringWriter();
                jw = new JsonTextWriter(sw);
                writers[type] = new KeyValuePair<JsonWriter, StringWriter>(jw, sw);
                jw.WriteStartArray();
            }
    
            HashSet<object> tbp;
            if (processed.ContainsKey(type))
            {
                toBeProcessed[type] = tbp = new HashSet<object>(appendix.Except(processed[type]));
            }
            else
            {
                toBeProcessed[type] = tbp = new HashSet<object>(appendix);
                processed[type] = new HashSet<object>();
            }
    
            if (tbp.Count > 0)
            {
                numAdditions += tbp.Count;
                foreach (object obj in tbp)
                {
                    serializer.Serialize(jw, obj); // Note, not writer, but jw--we write each type to its own JsonWriter and combine them later.
                }
                processed[type].UnionWith(tbp);
            }
    
            //TODO: Add traversal depth limiter!
        }
    } while (numAdditions > 0);
    
    if (payload.Appendices.Count > 0)
    {
        writer.WritePropertyName("linked");
        writer.WriteStartObject();
    
        // Okay, we should have captured everything now. Now combine the type writers into the main writer...
        foreach (KeyValuePair<Type, KeyValuePair<JsonWriter, StringWriter>> apair in writers)
        {
            apair.Value.Key.WriteEnd(); // close off the array
            writer.WritePropertyName(apair.Key.Name);
            writer.WriteRawValue(apair.Value.Value.ToString()); // write the contents of the type JsonWriter's StringWriter to the main JsonWriter
        }
    
        writer.WriteEndObject();
    }
    

    而且我知道我对Dictionary&lt;Type, KeyValuePair&lt;JsonWriter,StringWriter&gt;&gt; 的使用是一种令人讨厌的黑客行为。如果有人不能告诉我从 JsonWriter 中取回 StringWriter 的方法,我将为该对或其他东西创建一个结构。我想我可以创建一个结构来保存processedtoBeProcessedjsonWritertextWriter,并将它们全部放在一个Dictionary 中,由Type 键入。嗯……

    【讨论】:

      猜你喜欢
      • 2021-07-12
      • 2010-12-11
      • 2020-02-11
      • 2017-07-29
      • 2013-08-10
      • 2012-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多