Сериализация динамических объектов

Я попытался сериализовать класс DynamicObject с BinaryFormatter, но:

  • Выходной файл слишком большой, не совсем совместим с проводом
  • Циркулярные ссылки не обрабатываются (зависают при сериализации)

Поскольку сериализация DynamicObject сама по себе очень мало значит, вот класс, который я пытался сериализовать:

[Serializable()]
class Entity
    : DynamicObject, ISerializable
{

    IDictionary<string, object> values = new Dictionary<string, object>();

    public Entity()
    {

    }

    protected Entity(SerializationInfo info, StreamingContext ctx)
    {
        string fieldName = string.Empty;
        object fieldValue = null;

        foreach (var field in info)
        {
            fieldName = field.Name;
            fieldValue = field.Value;

            if (string.IsNullOrWhiteSpace(fieldName))
                continue;

            if (fieldValue == null)
                continue;

            this.values.Add(fieldName, fieldValue);
        }

    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        this.values.TryGetValue(binder.Name, out result);

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        this.values[binder.Name] = value;

        return true;
    }        

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {            
        foreach (var kvp in this.values)
        {
            info.AddValue(kvp.Key, kvp.Value);                 
        }
    }

}

(Думаю, я мог бы использовать ExpandoObject, но это уже другая история.)

Вот простая тестовая программа:

    static void Main(string[] args)
    {
        BinaryFormatter binFmt = new BinaryFormatter();

        dynamic obj = new Entity();
        dynamic subObj = new Entity();
        dynamic obj2 = null;

        obj.Value = 100;
        obj.Dictionary = new Dictionary<string, int>() { { "la la la", 1000 } };

        subObj.Value = 200;
        subObj.Name = "SubObject";

        obj.Child = subObj;

        using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
        {
            binFmt.Serialize(stream, obj);                
        }

        using (var stream = new FileStream("test.txt", FileMode.Open))
        {
            try
            {
                obj2 = binFmt.Deserialize(stream);                    
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }                
        }

        Console.ReadLine();

    }

Размещение некоторых точек останова здесь и там помогло мне взглянуть на содержимое obj2, и похоже, что исходные данные правильно десериализованы, хотя с указанными выше недостатками, если вы проявите изобретательность и перемещаете данные.

Я взглянул на protobuf-net Марка Гравелла, но я не совсем уверен, как использовать его в таком контексте (я даже не уверен, что взял правильную версию из репозитория, но эй).

Я знаю, что это больше кода, чем слов, но не думаю, что смогу объяснить сценарий лучше. Скажите, пожалуйста, есть ли что-то, что я могу добавить, чтобы прояснить этот вопрос.

Любая помощь горячо приветствуется.


person Raine    schedule 16.06.2010    source источник
comment
Для справки: protobuf-net в настоящее время не поддерживает dynamic. Я бы предложил перейти на уровень DTO для сериализации.   -  person Marc Gravell    schedule 17.06.2010
comment
@Marc - Спасибо, я займусь этим. Все еще открыт для других предложений.   -  person Raine    schedule 17.06.2010
comment
Что ж, в долгосрочной перспективе я планирую поддерживать это в protobuf-net. Но пока ничего не могу обещать.   -  person Marc Gravell    schedule 17.06.2010
comment
@Raine - Я запоздало придумал способ обрабатывать динамику с помощью protobuf-net Марка. См. ниже.   -  person sgtz    schedule 31.07.2011


Ответы (5)


Я на 98% уверен, что эта последовательность будет работать для динамического объекта.

  1. преобразовать объект в Expando Object
  2. приведите объект expando к типу Dictionary
  3. используйте ProtoBuf-net Serializer.Serialize / .Deserialize как обычно
  4. преобразовать словарь в Expando Object

Вы можете преобразовать объекты в коллекцию пар имя / значение для передачи.

Это лишь малая часть того, что может делать динамика, но, возможно, вам этого достаточно.

Есть специальный код для обработки некоторых из приведенных выше преобразований, который я могу показать вам, если есть интерес.

У меня нет решения, когда динамический является заполнителем для класса. В этом случае я бы предложил получить тип и использовать оператор switch для сериализации / десериализации по мере необходимости. В этом последнем случае вам нужно будет поместить что-то, чтобы указать, какой тип универсальной десериализации вам нужен (строка / идентификатор / полное имя типа / и т. Д.). Предполагается, что вы имеете дело со списком ожидаемых типов.

Примечание. Expando реализует IDictionary. Expando - это просто список пар ключ / значение. т.е. то, на чем вы ставите точки, - это ключ, а значение - это результат, возвращаемый любой цепочкой функций, реализующей это. Существует набор динамических интерфейсов для настройки синтаксического сахара, но в большинстве случаев вы не будете на них смотреть.

ссылки:

person sgtz    schedule 31.07.2011
comment
@ErikE: В то время я должен был сделать пример. Я поместил для вас несколько справочных ссылок в ответ. Надеюсь, это поможет. - person sgtz; 23.08.2014
comment
Это не работает, потому что ProtoBuf-net не будет сериализовать значения типа object. Базовый словарь имеет тип IDictionary<string, object>. - person Christiaan Wevers; 25.08.2016

Я не уверен, будет ли JSON приемлемым в вашем senario, но если это так, я использовал Json.net (http://json.codeplex.com) для сериализации динамических типов. Работает неплохо, шустрый, на выходе небольшой размер. Хотя Json.net не возвращает динамические объекты напрямую, очень легко преобразовать десериализованный вывод Json.Net в любой динамический тип. В приведенном ниже примере я использую ExpandoObject в качестве динамического типа. Приведенный ниже код - это то, что я использовал в Facebook Graph Toolkit. См. Ссылку на исходный источник: http://facebookgraphtoolkit.codeplex.com/SourceControl/changeset/view/48442#904504

public static dynamic Convert(string s) {
            object obj = Newtonsoft.Json.JsonConvert.DeserializeObject(s);
            if (obj is string) {
                return obj as string;
            } else {
                return ConvertJson((JToken)obj);
            }
    }

    private static dynamic ConvertJson(JToken token) {
        // FROM : http://blog.petegoo.com/archive/2009/10/27/using-json.net-to-eval-json-into-a-dynamic-variable-in.aspx
        // Ideally in the future Json.Net will support dynamic and this can be eliminated.
        if (token is JValue) {
            return ((JValue)token).Value;
        } else if (token is JObject) {
            ExpandoObject expando = new ExpandoObject();
            (from childToken in ((JToken)token) where childToken is JProperty select childToken as JProperty).ToList().ForEach(property => {
                ((IDictionary<string, object>)expando).Add(property.Name, ConvertJson(property.Value));
            });
            return expando;
        } else if (token is JArray) {
            List<ExpandoObject> items = new List<ExpandoObject>();
            foreach (JToken arrayItem in ((JArray)token)) {
                items.Add(ConvertJson(arrayItem));
            }
            return items;
        }
        throw new ArgumentException(string.Format("Unknown token type '{0}'", token.GetType()), "token");
    }
person Nathan Totten    schedule 28.06.2010
comment
спасибо, интересно. Я посмотрю как можно скорее. Меня беспокоит только то, что он (предположительно) не будет таким же эффективным, как двоичная сериализация. - person Raine; 29.06.2010
comment
После того, как я попробовал несколько разных решений моих проблем, ваш код был кодом, который делал то, что мне нужно. Итак, хотя ваш ответ, возможно, не помог OP так сильно, как мне, я все же ценю его поиск! :) - person Jason Bunting; 25.07.2011

Во-первых, размер вашего файла зависит от двух вещей (если я понимаю, как работает BinaryFormatter, поправьте меня, если я ошибаюсь):

  1. Размер сериализуемых фактических значений и
  2. Имена, используемые для сериализации значений объекта с помощью метода SerializationInfo.AddValue, которые хранятся в выходном файле, поэтому значения могут использоваться во время десериализации с тем же именем.

Очевидно, что №1 вызовет у вас самое большое замедление, которое можно уменьшить только путем оптимизации объектов, которые вы пытаетесь сериализовать.

Поскольку вы используете динамические объекты, почти незаметно небольшое увеличение размера, вызванное №2, неизбежно. Если бы вы знали типы и имена членов объекта заранее, вы могли бы просто дать каждому члену объекта очень короткое, последовательно определяемое имя («1», «2», «3» и т. Д.) Во время итерации. над членами объекта, добавляя их через SerializationInfo.AddValue. Затем во время десериализации вы можете использовать SerializationInfo.GetValue с тем же самым последовательно определяемым именем, и десериализация будет работать нормально, независимо от фактических имен десериализуемых значений, если вы выполняете итерацию по членам объекта в том же порядке, в котором они были добавлено. Конечно, это может сэкономить вам в среднем 4 или 5 байтов на член, но эти небольшие количества могут складываться в большие объекты.

@Raine: (Думаю, я мог бы использовать ExpandoObject, но это уже другая история.)

Не так; Я изменил ваш пример кода, чтобы использовать ExpandoObject вместо вашего Entity класса, и получил SerializationException. ExpandoObject не отмечен знаком SerializableAttribute, и у него нет соответствующих конструкторов для десериализации или сериализации. Однако это не означает, что вы не можете использовать ExpandoObject, если действительно хотите: он реализует IDictionary<string, object>, который, в свою очередь, реализует ICollection<KeyValuePair<string, object>>. Таким образом, экземпляр ExpandoObject - это набор KeyValuePair<string, object> экземпляров, которые помечены как сериализуемые. Итак, вы можете сериализовать ExpandoObject, но вам нужно будет преобразовать его как ICollection<KeyValuePair<string, object>> и сериализовать каждый KeyValuePair<string, object> в нем по отдельности. Однако это было бы бессмысленно с точки зрения оптимизации исходного образца кода, поскольку он занимает столько же места в файле.

Таким образом, я действительно не думаю, что есть какой-либо способ оптимизировать сериализацию динамического объекта - вам нужно перебирать элементы объекта каждый раз, когда он сериализуется, и у вас нет возможности заранее узнать размер объекта (по определению динамического) .

person Michael Hoffmann    schedule 25.04.2011

Я не знаю, поддерживает ли SharpSerializer динамические объекты, но, возможно, стоит попробовать:

http://www.sharpserializer.com/en/index.html

person Nikolai Sander    schedule 13.04.2011

Одно из предложений по сериализации динамического объекта - преобразовать их в String, а затем сериализовать, затем вы можете десериализовать обратно в свой объект, если это применимо в вашем случае.

person Suraj Verma    schedule 30.03.2021