Страница 1 из 1

Сериализация Generic Dictionary

СообщениеДобавлено: 08 авг 2014, 21:07
IDoNotExist
Собственно вопрос: какие есть варианты сериализации Dictionary?
Вот пример из документации, тут предлагается список ключей и значений записывать в два отдельных списка. Но что то мне такой вариант не очень нравится, не очень хотелось бы для каждого словаря создавать дополнительно два списка, будет явно переизбыток данных, понятно что списки можно очищать после десериализации, но всеравно не очень красивый вариант.
Когда мы объявляем просто сериализуемую переменную или массив, [unity 3D] как то же обходится без дополнительных костылей, сериализует и сохраняет данные где то у себя в недрах, могу ли я таким же образом сохранить данные в своем формате при сериализации компонента и вытащить их при его десериализации, без лишнего "жира"?

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 08 авг 2014, 21:23
Salamandr
убирайте ключи и сохраняйте только значения
(не мой пример)
Синтаксис:
Используется csharp
class herb_pos
{
var x:float;
var y:float;
var z:float;
}
 
var herbs : GameObject[];
var load_herbs=new Array();
var herb_proto : GameObject;
 
function Write()
{
var w: herb_pos  = new herb_pos();
herbs = GameObject.FindGameObjectsWithTag("herb");
var fs: FileStream  = new FileStream("herb.sbin", FileMode.Create);
var bf: BinaryFormatter  = new BinaryFormatter();
var herb_count:int=herbs.length;
bf.Serialize(fs, herb_count);
    for (var herb : GameObject in herbs) {
    w.x=herb.transform.position.x;
    w.y=herb.transform.position.y;
    w.z=herb.transform.position.z;
    bf.Serialize(fs, w);
    }
fs.Close();
}
 
function Read()
{  
var vf: IFormatter  = new BinaryFormatter();
var fs = new FileStream("herb.sbin", FileMode.Open, FileAccess.Read);
var herb_count:int=vf.Deserialize(fs);
    for (var i=0;i<herb_count;i++) {
    var w1: herb_pos= vf.Deserialize(fs);
    load_herbs.Add(Instantiate (herb_proto, Vector3(w1.x,w1.y,w1.z), herb_proto.transform.rotation));
    load_herbs[herb_count].name="herb"+herb_count;
    }
fs.Close();
}

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 08 авг 2014, 22:00
BenjaminMoore
IDoNotExist писал(а):Собственно вопрос: какие есть варианты сериализации Dictionary?

Если коротко - в таком виде никак.
Дженерик классы не сериализуются, за исключением специального костыля для листа
Всё
просто объявить у себя класс
Синтаксис:
Используется csharp
[System.Serializable]
class Foo<T>
{
   public T Value;
}
 


и объявите как поле
Синтаксис:
Используется csharp
class TestGeneric : MonoBehaviour
{
    public Foo<int> Test;
}
 


и Вы ничего не увидите в иснпекторе :)

хотя если сделаете так
Синтаксис:
Используется csharp
class FooInt : Foo<int>
 

и объявите его, он сериализуется
с дикшинари нужно проделать то же самое
благо он не sealed
наследуйте его и он должен сериализоваться :)

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 08 авг 2014, 22:37
IDoNotExist
Salamandr писал(а):убирайте ключи и сохраняйте только значения

Куда их убирать? В чем смысл? Если бы мне нужны были только значения я взял бы List, речь идет именно о Dictionary.

BenjaminMoore писал(а):
IDoNotExist писал(а):Собственно вопрос: какие есть варианты сериализации Dictionary?

Если коротко - в таком виде никак.

Я понимаю что никак, вопрос был о том как кастомизировать сериализацию.

BenjaminMoore писал(а):Дженерик классы не сериализуются, за исключением специального костыля для листа
Всё
просто объявить у себя класс
Синтаксис:
Используется csharp
[System.Serializable]
class Foo<T>
{
   public T Value;
}
 


и объявите как поле
Синтаксис:
Используется csharp
class TestGeneric : MonoBehaviour
{
    public Foo<int> Test;
}
 


и Вы ничего не увидите в иснпекторе :)

хотя если сделаете так
Синтаксис:
Используется csharp
class FooInt : Foo<int>
 

и объявите его, он сериализуется
с дикшинари нужно проделать то же самое
благо он не sealed
наследуйте его и он должен сериализоваться :)

Эммм, что именно то наследовать? Такой фокус "class FooDict : Dictionary<string,int>" с Dictionary не прокатит, [unity 3D] в принципе не знает как сериализовывать Dictionary.

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 08 авг 2014, 23:07
BlackMamba
имхо, самый простой способ сериализации/десериализации это использование BinaryFormatter - сериализует любой объект с атрибутом Serializable, если тот состоит из, опять же, сериализуемых полей, успешно работает с массивами таких типов. могу подкинуть пищу для размышлений "приблизительным кодом" - не факт, что сразу заработает.

Синтаксис:
Используется csharp
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
public class SerializableDictionary<K,V>
{
        [Serializable]
        private struct Pair
        {
                public K Key { get; set; }
                public V Value { get; set; }
        }

        private Pair[] _pairs;

        private SerializableDictionary() { }

        public static explicit operator Dictionary<K, V>(SerializableDictionary<K, V> from)
        {
                if (from == null) throw new ArgumentNullException("SerializableDictionary.explicit from");
                Dictionary<K, V> res = new Dictionary<K, V>();
                from._pairs.Select(p => res[p.Key] = p.Value).ToArray();
                return res;
        }
        public static explicit operator SerializableDictionary<K, V>(Dictionary<K, V> from)
        {
                if (from == null) throw new ArgumentNullException("SerializableDictionary.explicit from");
                SerializableDictionary<K, V> sd = new SerializableDictionary<K, V>()
                {
                        _pairs = from.Select(p => new Pair(){Key = p.Key, Value = p.Value}).ToArray()
                };
                return sd;
        }
       

        public void example()
        {
                Dictionary<int, string> source = new Dictionary<int, string>();
                byte[] dest = ((SerializableDictionary<int, string>)source).Serialize();
                source = (Dictionary<int, string>)(dest.Deserialize<int, string>());
        }
}
public static class Helpers
{
        public static byte[] Serialize<K,V>(this SerializableDictionary<K, V> from)
        {
                if (from == null) throw new ArgumentNullException("SerializableDictionary.Serialize from");
                byte[] res;
                using (MemoryStream ms = new MemoryStream())
                {
                        BinaryFormatter f = new BinaryFormatter();
                        f.Serialize(ms, from);

                        res = ms.ToArray();
                        ms.Close();

                }
                return res;
        }
        public static SerializableDictionary<K, V> Deserialize<K,V>(this byte[] from)
        {
                if (from == null) throw new ArgumentNullException("SerializableDictionary.Deserialize from");
                SerializableDictionary<K, V> to;
                using (MemoryStream ms = new MemoryStream(from))
                {
                        BinaryFormatter f = new BinaryFormatter();
                        to = (SerializableDictionary<K, V>)f.Deserialize(ms);
                        ms.Close();
                }
                return to;
        }
}


из опыта: K,V - встроенные типы и массивы типов, кроме uint, ushort и т.д., а также сериализуемые подобным образом иные типы и их массивы, кроме перечислений

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 09 авг 2014, 09:48
Neodrop

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 09 авг 2014, 15:56
Neodrop
Теперь есть интерфейс ISerializationCallbackReceiver реализующий вызовы методов перед сериализацией и после десериализации.
Ловим перед их сохраняем/загружаем наш Dictionary теми инструментами, что нам более нравятся.

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 09 авг 2014, 18:10
BenjaminMoore
Эммм, что именно то наследовать? Такой фокус "class FooDict : Dictionary<string,int>" с Dictionary не прокатит, [unity 3D] в принципе не знает как сериализовывать Dictionary.

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

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 09 авг 2014, 21:53
Neyl
Можно так.
Базовый generic класс.
Синтаксис:
Используется csharp
using System;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class SerializedDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{
    [SerializeField]
    private List<TKey> m_keys = new List<TKey>();
    [SerializeField]
    private List<TValue> m_values = new List<TValue>();


    public void OnBeforeSerialize()
    {
        m_keys.Clear();
        m_values.Clear();
        foreach (KeyValuePair<TKey, TValue> kvp in this)
        {
            m_keys.Add(kvp.Key);
            m_values.Add(kvp.Value);
        }
    }

    public void OnAfterDeserialize()
    {
        Clear();
        for (int i = 0; i != Math.Min(m_keys.Count, m_values.Count); i++)
        {
            Add(m_keys[i], m_values[i]);
        }
    }
}

От него наследовать уже нужные разновидности словарей, например
Синтаксис:
Используется csharp
[Serializable]
public class StringVector3Dictionary : SerializedDictionary<string, Vector3>
{
}

Пример использования
Синтаксис:
Используется csharp
using System;
using System.Globalization;
using UnityEngine;

public class SerializedDictionaryTester : MonoBehaviour
{
    public StringVector3Dictionary demoDictionary = new StringVector3Dictionary();


    void Start()
    {
        Debug.Log(demoDictionary.Count);
    }

    [ContextMenu("AddKeyValuePair")]
    void AddKeyValuePair()
    {
        demoDictionary.Add(DateTime.Now.ToString(CultureInfo.InvariantCulture), Vector3.up);
    }
}
 

Заполнять Dictionary придется через кастомный редактор.

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 12 авг 2014, 09:46
IDoNotExist
Именно про использование интерфейса ISerializationCallbackReceiver я и говорил в первом сообщении, меня интересовало как избежать использования двух списков и сохранять напрямую в какой либо ассет, в общем пока что ничего хорошего из этого не получилось, во - первых метод OnBeforeSerialize() вызывается при каждом OnInspectorUpdate() когда объект выделен, это значит что надо постоянно проверять актуальные ли данные записаны в файл, чтобы постоянно не писать туда, во вторых в документации написано, OnBeforeSerialize() и OnAfterDeserialize() могут быть вызваны не в основном потоке, тоже не понятно что при этом произойдет, можно конечно залочить основные объекты, но что то не охота экспериментировать, в общем пока что использую сериализацию словаря в два списка, так как это самый простой способ.

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 22 янв 2017, 13:51
smartxp
Вижу тема старая, но плодить одинаковые не хочется.
Сделал так, как описал Neyl - после ребилда кода, значения слетают, не срабатывает OnBeforeSerialize() и OnAfterDeserialize(), надо ли что-то дополнительно прописывать, что бы эти функции срабатывали.
Обычные типы данных (int, string - вне Dictionary) сохраняются. ScriptableObject не использую, может обязательно его использовать в данном случае?

В конце OnInspectorGUI прописано:
Синтаксис:
Используется csharp
 
if (GUI.changed)
{
     EditorUtility.SetDirty(this.target);
     serializedObject.ApplyModifiedProperties();
}
 


Облазил весь интернет, но так проблему и не решил. Версия unity 5.5.0f3.

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 22 янв 2017, 14:21
IDoNotExist
smartxp писал(а):Вижу тема старая, но плодить одинаковые не хочется.
Сделал так, как описал Neyl - после ребилда кода, значения слетают, не срабатывает OnBeforeSerialize() и OnAfterDeserialize(), надо ли что-то дополнительно прописывать, что бы эти функции срабатывали.
Обычные типы данных (int, string - вне Dictionary) сохраняются. ScriptableObject не использую, может обязательно его использовать в данном случае?

В конце OnInspectorGUI прописано:
Синтаксис:
Используется csharp
 
if (GUI.changed)
{
     EditorUtility.SetDirty(this.target);
     serializedObject.ApplyModifiedProperties();
}
 


Облазил весь интернет, но так проблему и не решил. Версия unity 5.5.0f3.


Так попробуйте:
Синтаксис:
Используется csharp
        Undo.RecordObject(target, GetType().Name);

        if (GUI.changed)
        {
            EditorUtility.SetDirty(target);
        }
 

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 22 янв 2017, 14:23
seaman
ISerializationCallbackReceiver не забыли?

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 22 янв 2017, 15:21
smartxp
Undo.RecordObject(target, GetType().Name);

if (GUI.changed)
{
EditorUtility.SetDirty(target);
}

Не помогло

ISerializationCallbackReceiver - нет не забыл.

Re: Сериализация Generic Dictionary

СообщениеДобавлено: 22 янв 2017, 19:07
IDoNotExist
smartxp писал(а):Не помогло

ISerializationCallbackReceiver - нет не забыл.

Тогда показывайте полный код скрипта и инспектора для него.