0

我正在 Unity 中使用 Json.NET 构建序列化系统。基本思想是需要序列化引用的类用 [Ref] 装饰,如下所示:

// Wine can be referenced, and these references need to be serialised as a numeric ID.
[Ref]
public class Wine
{
    private string name = "";

    public Wine(string _name)
    {
        name = _name;
    }
}

此类的任何其他实例都被自动视为引用,除非该字段已用 [Def] 修饰。

public class FoodExample
{
    // This central script defines all foods and wines in the program.
    [Def]
    List<Food> foods = new List<Food>();

    [Def]
    List<Wine> wines = new List<Wine>();
}

[Ref]
public class Food
{
    private string name = "";
    // Food references a wine that pairs well with it, but it does not define one. When serialised, this should be a numeric ID instead of a Json definition.
    private Wine pairsWellWith = null;

    public Food(string _name, Wine _pairsWellWith)
    {
        name = _name;
        pairsWellWith = _pairsWellWith;
    }
}

然后我可以使用一些自定义转换器(一个将类实例序列化为其定义,另一个作为数字 ID),甚至可以使用手动分配这些转换器的自定义合同解析器。当我只处理奇异字段时,这个系统效果很好。我可以说:

[Def]
Wine myFavouriteWine = null;

Wine refToFavouriteWine = null;

...而前者会变成一堆Json字段,而后者会变成简单的数值引用,避免引用重复。这很好用,因为我可以通过使用 CreateProperty() 覆盖来读取自定义合同解析器中的字段信息属性。或者我可以使用

[CustomConverter(typeof(RefConverter/DefConverter))]

但这不适用于集合。我可以将自定义行为应用于集合本身,但不能应用于其元素。我可以为该集合制作一个自定义转换器以对其子项进行操作,但我不确定如何。

这是一个有点复杂的场景,所以我暂时不会用大量代码淹没每个人,但如果需要更多细节,请说出来。

最后:我不使用 Json.NET 的本机引用处理系统的原因是因为我们有特殊需要将一些对象序列化为数字,而将其他对象序列化为清晰的字符串名称(例如“PinotGris_WINE”)。

谢谢。

4

1 回答 1

0

回答我自己的问题:

目前有两种解决方案。第一个是在 dbc 的评论中推荐的:简单地使用JsonPropertyAttribute.ItemConverterType来告诉 Json 使用DefinitionJConverterReferenceJConverter在装饰集合的元素上。

仅仅因为这个解决方案与我当前的其他方法不一致,并且因为我更喜欢较短的属性名称,所以我还将展示我自己的解决方案:

创建另外两个名为DefCollectionJConverter和的自定义转换器RefCollectionJConverter。在合约解析器中,将这些附加到任何发现的包含用[Ref]. 此外,如果集合本身已用 装饰[Def],则将其传递给DefCollectionJConverter,如下所示:

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
    JsonProperty property = base.CreateProperty(member, memberSerialization);

    // Do not handle properties (it's difficult to concieve of a time when properties should be serialised at all).
    if(member is PropertyInfo)
    {
        return property;
    }
    else if(member is FieldInfo)
    {
        FieldInfo field = ((FieldInfo)member);

        // Does this field implement IList? This Utility function gets all member interfaces and checks if any of them equal the argued interface.
        if (field.FieldType.ImplementsInterface<IList>())
        {
            // Are the elements of this IList decorated with [Ref]?
            if (field.FieldType.GetGenericArguments()[0].HasAttribute<RefAttribute>())
            {
                // Does this collection field have [Def]?
                if (field.HasAttribute<DefAttribute>())
                {
                    // This is a collection of definitions.
                    property.Converter = new DefCollectionJConverter();
                }
                else
                {
                    // This is a collection of references.
                    property.Converter = new RefCollectionJConverter();
                }
            }
        }
        else
        {
            // If the field's type class has been decorated with Ref.
            if (field.FieldType.HasAttribute<RefAttribute>())
            {
                if (member.HasAttribute<DefAttribute>())
                {
                    // If this particular field also has Def, it's a definition.
                    property.Converter = new DefinitionJConverter();
                }
                else
                {
                    // Else it's a reference.
                    property.Converter = new ReferenceJConverter();
                }
            }
        }
    }

    return property;
}

在 Collection 转换器本身内部,填充 aJArray然后调用JArray.WriteTo(). JToken使用定义的默认序列化程序或引用的自定义引用序列化程序构造s:

public class DefCollectionJConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JArray array = new JArray();
        IList list = (IList)value;

        if (list.Count > 0)
        {
            // Populate the JArray with JTokens from the list elements. Pass the current serializer, which should contain default settings and converters.
            // This should handle child objects appropriately, too.
            JsonSerializer defSerializer = new JsonSerializer();
            defSerializer.Converters.Add(new DefinitionJConverter());

            for (int e = 0; e < list.Count; e++)
            {
                JToken first = JToken.FromObject(list[e], serializer);
                array.Add(first);
            }

            array.WriteTo(writer);
        }
        else
        {
            Debug.LogError("List was empty.");
        }
    }
}

public class RefCollectionJConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JArray array = new JArray();
        IList list = (IList)value;

        if(list.Count > 0)
        {
            // Populate the JArray with JTokens of the list elements, using a serialiser that only contains ReferenceJConverter.
            // Note that we don't need to worry about this token's children: only definitions can describe children.
            JsonSerializer refSerializer = new JsonSerializer();
            refSerializer.Converters.Add(new ReferenceJConverter());

            for(int e = 0; e < list.Count; e++)
            {
                JToken first = JToken.FromObject(list[e], refSerializer);
                array.Add(first);
            }

            array.WriteTo(writer);
        }
        else
        {
            Debug.LogError("List was empty.");
        }
    }
}

对于这些转换器,我CanConvert()总是返回 true,因为我是手动分配这些转换器的。

我还没有用反序列化测试过这个,但我不明白为什么它不起作用。

于 2018-08-21T11:13:57.817 回答