Newtonsoft JSON.NET создает словарь типов - PullRequest
0 голосов
/ 27 сентября 2018

У меня есть некоторый код сериализации, основанный на пакете 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, но это содержимое ... вместо того, чтобы писать полное-полное имя сборки, я хотел бы иметь индекс, который будет его представлять.

Спасибо

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

У меня были похожие требования, вот как я это сделал:

  • Создание собственного класса 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

0 голосов
/ 27 сентября 2018

Вы можете определить пользовательские типы, используя пользовательский Связыватель сериализации .

IE

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...