├── .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 | 
2 |
3 | # Unity - Serializable Dictionary
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | -----------------------------------------------------------------------
15 |
16 |
17 |
18 |
19 |
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 |
--------------------------------------------------------------------------------