diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4449806..897c67c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ Fixes
### MLEM.Data
Additions
- Allow RuntimeTexturePacker to automatically dispose submitted textures when packing
+- Added JsonTypeSafeWrapper and JsonTypeSafeGenericDataHolder
Improvements
- Use TitleContainer for opening streams where possible
diff --git a/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs b/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs
new file mode 100644
index 0000000..bfb98b5
--- /dev/null
+++ b/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using MLEM.Misc;
+using Newtonsoft.Json;
+
+namespace MLEM.Data.Json {
+ ///
+ /// An represents an object that can hold generic key-value based data.
+ /// This class uses for each object stored to ensure that objects with a custom get deserialized as an instance of their original type if is not set to .
+ ///
+ [DataContract]
+ public class JsonTypeSafeGenericDataHolder : IGenericDataHolder {
+
+ [DataMember(EmitDefaultValue = false)]
+ private Dictionary data;
+
+ ///
+ public void SetData(string key, T data) {
+ if (EqualityComparer.Default.Equals(data, default)) {
+ if (this.data != null)
+ this.data.Remove(key);
+ } else {
+ if (this.data == null)
+ this.data = new Dictionary();
+ this.data[key] = new JsonTypeSafeWrapper(data);
+ }
+ }
+
+ ///
+ public T GetData(string key) {
+ if (this.data != null && this.data.TryGetValue(key, out var val))
+ return (T) val.Value;
+ return default;
+ }
+
+ ///
+ public IEnumerable GetDataKeys() {
+ if (this.data == null)
+ return Array.Empty();
+ return this.data.Keys;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/MLEM.Data/Json/JsonTypeSafeWrapper.cs b/MLEM.Data/Json/JsonTypeSafeWrapper.cs
new file mode 100644
index 0000000..f6191ec
--- /dev/null
+++ b/MLEM.Data/Json/JsonTypeSafeWrapper.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+
+namespace MLEM.Data.Json {
+ ///
+ /// A json type-safe wrapper can be used to wrap any objects of any type before submitting them to a non-specifically typed or that will be serialized using a in cases where is not set to .
+ /// If any object is not wrapped in this manner, and it has a custom , the value deserialized from it might not have the same type as the originally serialized object. This behavior can be observed, for example, when serializing a of entries, one of which is a : The will be serialized as a and, upon deserialization, will remain a .
+ /// In general, wrapping objects in this manner is only useful in rare cases, where custom data of an unexpected or unknown type is stored.
+ /// See for an example of how this class and can be used, and see this stackoverflow answer for more information on the problem that this class solves: https://stackoverflow.com/a/38798114.
+ ///
+ public abstract class JsonTypeSafeWrapper {
+
+ ///
+ /// Returns this json type-safe wrapper's value as an .
+ ///
+ public abstract object Value { get; }
+
+ }
+
+ ///
+ [DataContract]
+ public class JsonTypeSafeWrapper : JsonTypeSafeWrapper {
+
+ ///
+ public override object Value => this.value;
+ [DataMember]
+ private readonly T value;
+
+ ///
+ /// Creates a new json type-safe wrapper instance that wraps the given .
+ ///
+ /// The value to wrap
+ public JsonTypeSafeWrapper(T value) {
+ this.value = value;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/MLEM.Ui/Style/UiStyle.cs b/MLEM.Ui/Style/UiStyle.cs
index 6901f05..47d9acb 100644
--- a/MLEM.Ui/Style/UiStyle.cs
+++ b/MLEM.Ui/Style/UiStyle.cs
@@ -11,7 +11,7 @@ namespace MLEM.Ui.Style {
///
/// The style settings for a .
/// Each uses these style settings by default, however you can also change these settings per element using the elements' individual style settings.
- /// Note that this class is a , meaning additional styles for custom components can easily be added using
+ /// Note that this class is a , meaning additional styles for custom components can easily be added using
///
public class UiStyle : GenericDataHolder {
diff --git a/MLEM/Misc/GenericDataHolder.cs b/MLEM/Misc/GenericDataHolder.cs
index c36a7e9..120d3d9 100644
--- a/MLEM/Misc/GenericDataHolder.cs
+++ b/MLEM/Misc/GenericDataHolder.cs
@@ -11,8 +11,8 @@ namespace MLEM.Misc {
private Dictionary data;
///
- public void SetData(string key, object data) {
- if (data == default) {
+ public void SetData(string key, T data) {
+ if (EqualityComparer.Default.Equals(data, default)) {
if (this.data != null)
this.data.Remove(key);
} else {
@@ -30,7 +30,7 @@ namespace MLEM.Misc {
}
///
- public IReadOnlyCollection GetDataKeys() {
+ public IEnumerable GetDataKeys() {
if (this.data == null)
return Array.Empty();
return this.data.Keys;
@@ -49,7 +49,8 @@ namespace MLEM.Misc {
///
/// The key to store the data by
/// The data to store in the object
- void SetData(string key, object data);
+ /// The type of the data to store
+ void SetData(string key, T data);
///
/// Returns a piece of generic data of the given type on this object.
@@ -63,7 +64,7 @@ namespace MLEM.Misc {
/// Returns all of the generic data that this object stores.
///
/// The generic data on this object
- IReadOnlyCollection GetDataKeys();
+ IEnumerable GetDataKeys();
}
}
\ No newline at end of file
diff --git a/Tests/DataTests.cs b/Tests/DataTests.cs
index ba210e1..4d602f2 100644
--- a/Tests/DataTests.cs
+++ b/Tests/DataTests.cs
@@ -92,6 +92,31 @@ namespace Tests {
Assert.AreEqual(Or(flags[24], flags[43]).ToString(), "Flag24 | Flag43");
}
+ [Test]
+ public void TestJsonTypeSafety() {
+ var serializer = new JsonSerializer {TypeNameHandling = TypeNameHandling.Auto};
+
+ // normal generic data holder should crush the time span down to a string due to its custom serializer
+ var data = new GenericDataHolder();
+ data.SetData("Time", TimeSpan.FromMinutes(5));
+ var read = SerializeAndDeserialize(serializer, data);
+ Assert.IsNotInstanceOf(read.GetData