【问题标题】:Newtonsoft JSON.NET create a Type dictionaryNewtonsoft JSON.NET 创建类型字典
【发布时间】:2018-09-27 12:38:16
【问题描述】:

我有一些基于 Newtonsoft Json.NET 包的序列化代码。
我序列化大量少数类型的实例, 但是 JSON.NET 添加一个标签,例如"$type": "complex_serializer_tests.SerializerTests+Node, complex-serializer-tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", 到每个元素。

这会显着增加保存格式的大小,我想消除它。

我想创建一个类型字典,它将:
1.为每个新类型分配一个Id(整数) 2. 并在 JSON 中使用它,沿线 "$type":#105
同时添加 type-id => type-name 元素。

对不起,这不是很具体,
但问题是我不知道如何解决它,并且希望得到一些指导,我应该阅读哪些主题......

编辑澄清,我不介意 $type 属性名称,但它的内容...而不是写程序集全限定名称,我想要一个代表它的索引。

谢谢

【问题讨论】:

标签: c# serialization json.net


【解决方案1】:

您可以使用自定义Serialization Binder 定义自定义类型。

I.E.

public class MyBinder : ISerializationBinder
{
    public Dictionary<string,Type> Types { get; set; }

    public Type BindToType(string assemblyName, string typeName)
    {
        // probably want to add some error handling here
        return Types[typeName];
    }

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        // not very efficient, but could have a separate reverse dictionary
        typeName= Types.First(t => t.Value == serializedType).Value;
    }
}

var settings = new JsonSerializerSettings { SerializationBinder = new MyBinder { ... } };

另外,如果它在可以推断的地方添加类型名称,您可以在 JsonSerializerSettings 中指定何时添加它们,尽管这可能会影响反序列化,具体取决于您要反序列化的类型。

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None };
JsonConvert.SerializeObject(obj, settings);

【讨论】:

【解决方案2】:

我有类似的要求,我是这样做的:

  • 创建自定义 JsonConverter 类
  • 告诉序列化器使用你自定义的 JsonConverter

JsonConverter 示例

请注意,这是字母代码,您将不得不更改部件,尤其是。 GetAllItemTypes 将类型键初始化为类型映射(已知限制:需要锁定)。

public class TypePropertyConverter : JsonConverter
{
    /// <summary>
    /// During write, we have to return CanConvert = false to be able to user FromObject internally w/o "self referencing loop" errors.
    /// </summary>
    private bool _isInWrite = false;

    public override bool CanWrite => !_isInWrite;

    private static Dictionary<string, Type> _allItemTypes;
    public static Dictionary<string, Type> AllItemTypes => _allItemTypes ?? (_allItemTypes = GetAllItemTypes());

    /// <summary>
    /// Read all types with JsonType or BsonDiscriminator attribute from current assembly.
    /// </summary>
    /// <returns></returns>
    public static Dictionary<string, Type> GetAllItemTypes()
    {
        var allTypesFromApiAndCore = typeof(TypePropertyConverter)
            .Assembly
            .GetTypes()
            .Concat(typeof(OrdersCoreRegistry)
                .Assembly
                .GetTypes());

        var dict = new Dictionary<string, Type>();
        foreach (var type in allTypesFromApiAndCore)
        {
            if (type.GetCustomAttributes(false).FirstOrDefault(a => a is JsonTypeAttribute) is JsonTypeAttribute attr)
            {
                dict.Add(attr.TypeName, type);
            }
            else if (type.GetCustomAttributes(false).FirstOrDefault(a => a is BsonDiscriminatorAttribute) is BsonDiscriminatorAttribute bda)
            {
                dict.Add(bda.Discriminator, type);
            }
        }
        return dict;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        _isInWrite = true;

        try
        {
            var type = value.GetType();
            var typeKey = AllItemTypes.First(kv => kv.Value == type).Key;

            var jObj = JObject.FromObject(value, serializer);
            jObj.AddFirst(new JProperty("type", typeKey));
            jObj.WriteTo(writer);
        }
        finally
        {
            _isInWrite = false;
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        // we need to read and remove the "type" property first
        var obj = JObject.Load(reader);
        var typeKey = obj["type"];
        if (typeKey == null)
        {
            throw new InvalidOperationException("Cannot deserialize object w/o 'type' property.");
        }

        obj.Remove("type");

        // create object
        if (!AllItemTypes.TryGetValue(typeKey.Value<string>(), out var type))
        {
            throw new InvalidOperationException($"No type registered for key '{typeKey}'. Annotate class with JsonType attribute.");
        }

        var contract = serializer.ContractResolver.ResolveContract(type);
        var value = contract.DefaultCreator();

        if (value == null)
        {
            throw new JsonSerializationException("No object created.");
        }

        using (var subReader = obj.CreateReader())
        {
            serializer.Populate(subReader, value);
        }

        return value;
    }

    public override bool CanConvert(Type objectType)
    {
        return AllItemTypes.Any(t => t.Value == objectType);
    }
}

它正在寻找一个自定义属性“JsonType”,并将使用其 Name 属性值作为键。如果没有找到 JsonType,它将寻找 BsonDiscriminator 属性(来自 mongodb)作为后备。您将不得不调整这部分。

告诉 Serializer 关于你的 JsonConverter

有多种方法可以做到这一点。我正在使用这样的属性:

对列表项使用转换器:

    [JsonProperty(ItemConverterType = typeof(TypePropertyConverter))]
    public List<PipelineTrigger> Triggers { get; set; }

详情请见https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_JsonProperty.htm

或者您可以将 JsonConverter 属性添加到您的基类:https://www.newtonsoft.com/json/help/html/JsonConverterAttributeClass.htm

【讨论】:

  • 感谢您的帖子...您是否按照@EricDamtoft 的建议评估了 ISerializationBinder 并决定使用这种方法?或者这是您尝试过的唯一选择?
  • 我必须实现一个已定义的 API,该 API 使用具有不同名称的类型属性。此外,我可能必须添加功能来处理 json 结构的更多变化。但是 Eric 是对的,ISerializationBinder 方法应该更容易。忘记了。
  • 感谢分享。我喜欢替代品...总是要学习的东西:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
  • 2013-03-23
  • 2019-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多