├── .gitignore ├── LICENSE ├── README.md └── SerializableDictionary.cs /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Recordings can get excessive in size 18 | /[Rr]ecordings/ 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | *.app 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | # Packed Addressables 68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 69 | 70 | # Temporary auto-generated Android Assets 71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 72 | /[Aa]ssets/[Ss]treamingAssets/aa/* 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JDSherbert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/JDSherbert/Unity-Serializable-Dictionary/assets/43964243/bf425086-d5ad-47b6-b38c-770e903fbf0e) 2 | 3 | # Unity - Serializable Dictionary 4 | 5 | 6 | 7 | 8 | Stars Badge 9 | Forks Badge 10 | Watchers Badge 11 | Issues Badge 12 | 13 |

14 | ----------------------------------------------------------------------- 15 | 16 | Extension Tool For Unity 17 | 18 | 19 | License 20 | 21 | ----------------------------------------------------------------------- 22 | 23 | ## Overview 24 | 25 | Dictionaries cannot be serialized and displayed in the Unity inspector as is. 26 | This pisses me off greatly, so I decided to write a quick and dirty code class to allow Dictionary Serialization in Unity. 27 | Add this script to your project and discover the power of Dictionary Serialization! 28 | 29 | To achieve serialization, the Serializable Dictionary class implements the `ISerializationCallbackReceiver` interface. This interface provides two callback methods: `OnBeforeSerialize()` and `OnAfterDeserialize()`. It also implements the `IDictionary` interface, allowing use of standard Dictionary behaviours. 30 | 31 | By using the Serializable Dictionary class, you can easily create and manage key-value pairs directly in the Unity editor, allowing for dynamic customization and modification of data. This is particularly useful for scenarios where you need to store and modify structured data that should persist between editor sessions or during gameplay. 32 | 33 | Note that while the Serializable Dictionary class provides a convenient way to work with serialized dictionaries in Unity, it is not a built-in Unity feature. I've tried my best to replicate the default Dictionary featureset but there may be some variance. 34 | 35 | ### Features: 36 | - Allows Dictionary display in the inspector. 37 | - Allows values to be serialized. 38 | - Functions (mostly) like a standard C# Dictionary class. 39 | 40 | ### Usage: 41 | 1. Simply add this script to your project. 42 | 2. If you haven't got it, I'd recommend grabbing [Odin Inspector](https://github.com/TeamSirenix/odin-serializer) to make it look prettier! 43 | 44 | Example Usage: 45 | 46 | ```cs 47 | using UnityEngine; 48 | using Sherbert.Framework.Generic; 49 | 50 | public class DictionaryExample : MonoBehaviour 51 | { 52 | [SerializeField] private SerializableDictionary myDictionary = new(); 53 | } 54 | ``` 55 | 56 | ----------------------------------------------------------------------- 57 | 58 | ## Prerequisites 59 | 60 | If you haven't got it, I'd strongly recommend grabbing Odin Inspector to make your serialized variables look prettier. 61 | You can grab Odin from here: 62 | 63 | - Github: https://github.com/TeamSirenix/odin-serializer 64 | - Unity Asset Store: https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041 65 | 66 | ----------------------------------------------------------------------- 67 | 68 | -------------------------------------------------------------------------------- /SerializableDictionary.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// ©2024 JDSherbert. All rights reserved. 3 | /// 4 | 5 | namespace Sherbert.Framework.Generic 6 | { 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | using System.Runtime.Serialization; 11 | using UnityEngine; 12 | 13 | // ---------------------------------------------------------------- // 14 | //? Serialized Dictionary Base 15 | // ---------------------------------------------------------------- // 16 | 17 | /// 18 | /// Serialized Dictionary Base class. 19 | /// 20 | public abstract class SerializableDictionary_Base 21 | { 22 | protected class Dictionary : System.Collections.Generic.Dictionary 23 | { 24 | public Dictionary() { } 25 | public Dictionary(IDictionary dictionary) : base(dictionary) { } 26 | public Dictionary(SerializationInfo serialInfo, StreamingContext streamingContext) : base(serialInfo, streamingContext) { } 27 | } 28 | 29 | public abstract class Cache { } 30 | } 31 | 32 | 33 | // ---------------------------------------------------------------- // 34 | //? Serialized Dictionary Base 35 | // ---------------------------------------------------------------- // 36 | 37 | /// 38 | /// Serialized Dictionary Base class. 39 | /// 40 | [Serializable] 41 | public abstract class SerializableDictionary_Base : SerializableDictionary_Base, IDictionary, IDictionary, ISerializationCallbackReceiver, IDeserializationCallback, ISerializable 42 | { 43 | Dictionary dictionary; 44 | [SerializeField] TKey[] keys; 45 | [SerializeField] TValueCache[] values; 46 | 47 | // ---------------------------------------------------------------- // 48 | 49 | public SerializableDictionary_Base() 50 | { 51 | this.dictionary = new Dictionary(); 52 | } 53 | 54 | public SerializableDictionary_Base(IDictionary dictionary) 55 | { 56 | this.dictionary = new Dictionary(dictionary); 57 | } 58 | 59 | // ---------------------------------------------------------------- // 60 | 61 | public void OnAfterDeserialize() 62 | { 63 | if (keys != null && values != null && keys.Length == values.Length) 64 | { 65 | dictionary.Clear(); 66 | int length = keys.Length; 67 | for (int i = 0; i < length; ++i) 68 | { 69 | dictionary[keys[i]] = GetValue(values, i); 70 | } 71 | 72 | keys = null; 73 | values = null; 74 | } 75 | } 76 | 77 | public void OnBeforeSerialize() 78 | { 79 | int count = dictionary.Count; 80 | keys = new TKey[count]; 81 | values = new TValueCache[count]; 82 | 83 | int i = 0; 84 | foreach (var kvp in dictionary) 85 | { 86 | keys[i] = kvp.Key; 87 | SetValue(values, i, kvp.Value); 88 | ++i; 89 | } 90 | } 91 | 92 | // ---------------------------------------------------------------- // 93 | 94 | protected abstract void SetValue(TValueCache[] cache, int i, TValue value); 95 | protected abstract TValue GetValue(TValueCache[] cache, int i); 96 | 97 | // ---------------------------------------------------------------- // 98 | 99 | /// 100 | /// Replaces the values in this dictionary with values from a different dictionary. 101 | /// 102 | /// Dictionary reference to copy from. 103 | public void CopyFrom(IDictionary dictionary) 104 | { 105 | this.dictionary.Clear(); 106 | foreach (var kvp in dictionary) 107 | { 108 | this.dictionary[kvp.Key] = kvp.Value; 109 | } 110 | } 111 | 112 | // ---------------------------------------------------------------- // 113 | #region IDictionary 114 | // ---------------------------------------------------------------- // 115 | 116 | public ICollection Keys { get { return ((IDictionary)dictionary).Keys; } } 117 | public ICollection Values { get { return ((IDictionary)dictionary).Values; } } 118 | public int Count { get { return ((IDictionary)dictionary).Count; } } 119 | public bool IsReadOnly { get { return ((IDictionary)dictionary).IsReadOnly; } } 120 | 121 | public TValue this[TKey key] 122 | { 123 | get { return ((IDictionary)dictionary)[key]; } 124 | set { ((IDictionary)dictionary)[key] = value; } 125 | } 126 | 127 | // ---------------------------------------------------------------- // 128 | 129 | /// 130 | /// Adds a new Key Value Pair to this dictionary. 131 | /// 132 | /// New key to add. 133 | /// New value to add. 134 | public void Add(TKey key, TValue value) 135 | { 136 | ((IDictionary)dictionary).Add(key, value); 137 | } 138 | 139 | /// 140 | /// Check to see if the dictionary contains the key. 141 | /// 142 | /// Key reference to check. 143 | /// true if the dictionary contains the key. 144 | public bool ContainsKey(TKey key) 145 | { 146 | return ((IDictionary)dictionary).ContainsKey(key); 147 | } 148 | 149 | /// 150 | /// Removes a Key Value Pair from the dictionary, by key reference. 151 | /// 152 | /// Key reference to remove. 153 | /// true if the key was removed successfully. 154 | public bool Remove(TKey key) 155 | { 156 | return ((IDictionary)dictionary).Remove(key); 157 | } 158 | 159 | /// 160 | /// Attempts to retrieve a Value from the dictionary, by key reference. 161 | /// 162 | /// Key reference. 163 | /// Value reference to write out to. 164 | /// true if the value was retrieved successfully. 165 | public bool TryGetValue(TKey key, out TValue value) 166 | { 167 | return ((IDictionary)dictionary).TryGetValue(key, out value); 168 | } 169 | 170 | /// 171 | /// Adds a new Key Value Pair to this dictionary. 172 | /// 173 | /// New key value pair to add. 174 | public void Add(KeyValuePair item) 175 | { 176 | ((IDictionary)dictionary).Add(item); 177 | } 178 | 179 | /// 180 | /// Removes all Key Value Pairs in this dictionary. 181 | /// 182 | public void Clear() 183 | { 184 | ((IDictionary)dictionary).Clear(); 185 | } 186 | 187 | /// 188 | /// Check to see if the dictionary contains the key value pair. 189 | /// 190 | /// Key value pair reference to check. 191 | /// true if the dictionary contains the key value pair. 192 | public bool Contains(KeyValuePair item) 193 | { 194 | return ((IDictionary)dictionary).Contains(item); 195 | } 196 | 197 | /// 198 | /// Appends a range of key value pairs into an index in the dictionary. 199 | /// 200 | /// Key value pair references to append. 201 | /// Array index to append at. 202 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 203 | { 204 | ((IDictionary)dictionary).CopyTo(array, arrayIndex); 205 | } 206 | 207 | /// 208 | /// Removes a Key Value Pair from the dictionary. 209 | /// 210 | /// Key value pair to remove. 211 | /// true if the key value pair was removed successfully. 212 | public bool Remove(KeyValuePair item) 213 | { 214 | return ((IDictionary)dictionary).Remove(item); 215 | } 216 | 217 | public IEnumerator> GetEnumerator() 218 | { 219 | return ((IDictionary)dictionary).GetEnumerator(); 220 | } 221 | 222 | IEnumerator IEnumerable.GetEnumerator() 223 | { 224 | return ((IDictionary)dictionary).GetEnumerator(); 225 | } 226 | 227 | // ---------------------------------------------------------------- // 228 | #endregion IDictionary 229 | // ---------------------------------------------------------------- // 230 | 231 | 232 | // ---------------------------------------------------------------- // 233 | #region IDictionary 234 | // ---------------------------------------------------------------- // 235 | 236 | public bool IsFixedSize { get { return ((IDictionary)dictionary).IsFixedSize; } } 237 | ICollection IDictionary.Keys { get { return ((IDictionary)dictionary).Keys; } } 238 | ICollection IDictionary.Values { get { return ((IDictionary)dictionary).Values; } } 239 | public bool IsSynchronized { get { return ((IDictionary)dictionary).IsSynchronized; } } 240 | public object SyncRoot { get { return ((IDictionary)dictionary).SyncRoot; } } 241 | 242 | public object this[object key] 243 | { 244 | get { return ((IDictionary)dictionary)[key]; } 245 | set { ((IDictionary)dictionary)[key] = value; } 246 | } 247 | 248 | // ---------------------------------------------------------------- // 249 | 250 | public void Add(object key, object value) 251 | { 252 | ((IDictionary)dictionary).Add(key, value); 253 | } 254 | 255 | public bool Contains(object key) 256 | { 257 | return ((IDictionary)dictionary).Contains(key); 258 | } 259 | 260 | IDictionaryEnumerator IDictionary.GetEnumerator() 261 | { 262 | return ((IDictionary)dictionary).GetEnumerator(); 263 | } 264 | 265 | // ---------------------------------------------------------------- // 266 | 267 | /// 268 | /// Removes a Key Value Pair from the dictionary. 269 | /// 270 | /// Key reference to remove. 271 | /// true if the key value pair was removed successfully. 272 | public void Remove(object key) 273 | { 274 | ((IDictionary)dictionary).Remove(key); 275 | } 276 | 277 | /// 278 | /// Appends a range of key value pairs into an index in the dictionary. 279 | /// 280 | /// Key value pair references to append. 281 | /// Array index to append at. 282 | public void CopyTo(Array array, int index) 283 | { 284 | ((IDictionary)dictionary).CopyTo(array, index); 285 | } 286 | 287 | // ---------------------------------------------------------------- // 288 | #endregion IDictionary 289 | // ---------------------------------------------------------------- // 290 | 291 | // ---------------------------------------------------------------- // 292 | #region IDeserializationCallback 293 | // ---------------------------------------------------------------- // 294 | 295 | public void OnDeserialization(object sender) 296 | { 297 | ((IDeserializationCallback)dictionary).OnDeserialization(sender); 298 | } 299 | 300 | // ---------------------------------------------------------------- // 301 | #endregion IDeserializationCallback 302 | // ---------------------------------------------------------------- // 303 | 304 | // ---------------------------------------------------------------- // 305 | #region ISerializable 306 | // ---------------------------------------------------------------- // 307 | 308 | protected SerializableDictionary_Base(SerializationInfo info, StreamingContext context) 309 | { 310 | dictionary = new Dictionary(info, context); 311 | } 312 | 313 | public void GetObjectData(SerializationInfo info, StreamingContext context) 314 | { 315 | ((ISerializable)dictionary).GetObjectData(info, context); 316 | } 317 | 318 | // ---------------------------------------------------------------- // 319 | #endregion ISerializable 320 | // ---------------------------------------------------------------- // 321 | } 322 | 323 | // ---------------------------------------------------------------- // 324 | //? Serializable Dictionary 325 | // ---------------------------------------------------------------- // 326 | public static class SerializableDictionary 327 | { 328 | public class Cache : SerializableDictionary_Base.Cache 329 | { 330 | public T data; 331 | } 332 | } 333 | 334 | // ---------------------------------------------------------------- // 335 | //? Serializable Dictionary 336 | // ---------------------------------------------------------------- // 337 | 338 | /// 339 | /// Serialized Dictionary Class. Allows a dictionary-like data structure with key-value pairs that can be serialized and edited in the Unity editor. 340 | /// It addresses the limitation of Unity's default Dictionary class, which cannot be directly serialized. 341 | /// and . 342 | /// 343 | /// 344 | /// // Example usage in a MonoBehaviour script 345 | /// using UnityEngine; 346 | /// using Sherbert.Framework.Generic; 347 | /// 348 | /// public class DictionaryExample : MonoBehaviour 349 | /// { 350 | /// [SerializeField] private SerializableDictionary myDictionary = new(); 351 | /// } 352 | /// 353 | /// 354 | [Serializable] 355 | public class SerializableDictionary : SerializableDictionary_Base 356 | { 357 | public SerializableDictionary() { } 358 | public SerializableDictionary(IDictionary dict) : base(dict) { } 359 | protected SerializableDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } 360 | 361 | // ---------------------------------------------------------------- // 362 | 363 | protected override TValue GetValue(TValue[] values, int i) 364 | { 365 | return values[i]; 366 | } 367 | 368 | protected override void SetValue(TValue[] values, int i, TValue value) 369 | { 370 | values[i] = value; 371 | } 372 | 373 | // ---------------------------------------------------------------- // 374 | } 375 | 376 | // ---------------------------------------------------------------- // 377 | //? Serializable Dictionary 378 | // ---------------------------------------------------------------- // 379 | 380 | /// 381 | /// Serialized Dictionary Class. Allows a dictionary-like data structure with key-value pairs that can be serialized and edited in the Unity editor. 382 | /// It addresses the limitation of Unity's default Dictionary class, which cannot be directly serialized. 383 | /// and . 384 | /// 385 | /// 386 | /// // Example usage in a MonoBehaviour script 387 | /// using UnityEngine; 388 | /// using Sherbert.Framework.Generic; 389 | /// 390 | /// public class DictionaryExample : MonoBehaviour 391 | /// { 392 | /// [SerializeField] private SerializableDictionary myDictionary = new(); 393 | /// } 394 | /// 395 | /// 396 | [Serializable] 397 | public class SerializableDictionary : SerializableDictionary_Base where TValueCache : SerializableDictionary.Cache, new() 398 | { 399 | public SerializableDictionary() { } 400 | public SerializableDictionary(IDictionary dictionary) : base(dictionary) { } 401 | protected SerializableDictionary(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } 402 | 403 | // ---------------------------------------------------------------- // 404 | 405 | protected override TValue GetValue(TValueCache[] cache, int i) 406 | { 407 | return cache[i].data; 408 | } 409 | 410 | protected override void SetValue(TValueCache[] cache, int i, TValue value) 411 | { 412 | cache[i] = new TValueCache(); 413 | cache[i].data = value; 414 | } 415 | 416 | // ---------------------------------------------------------------- // 417 | } 418 | } 419 | --------------------------------------------------------------------------------