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

Благодарность

1
Tomer W 27 Сен 2018 в 15:38

2 ответа

Лучший ответ

Вы можете определить настраиваемые типы с помощью настраиваемого связывателя сериализации.

И.Е.

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
Eric Damtoft 27 Сен 2018 в 13:09

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

  • Создать собственный класс 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) в качестве запасного варианта. Вам нужно будет отрегулировать эту часть.

Расскажите сериализатору о себе JsonConverter

Есть несколько способов сделать это. Я использую такие атрибуты:

Используйте конвертер для элементов списка:

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

См. https://www.newtonsoft.com/json/help/html/T_NewSoftial_JS. .htm, чтобы узнать подробнее.

Или вы можете добавить атрибут JsonConverter к своему базовому классу: https: //www.newtonsoft .com / JSON / помощь / HTML / JsonConverterAttributeClass.htm

1
Christoph Lütjen 27 Сен 2018 в 13:03