mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-22 12:58:33 +01:00
Added JsonTypeSafeWrapper and JsonTypeSafeGenericDataHolder
This commit is contained in:
parent
778d416774
commit
8e83cc06a6
6 changed files with 118 additions and 6 deletions
|
@ -38,6 +38,7 @@ Fixes
|
||||||
### MLEM.Data
|
### MLEM.Data
|
||||||
Additions
|
Additions
|
||||||
- Allow RuntimeTexturePacker to automatically dispose submitted textures when packing
|
- Allow RuntimeTexturePacker to automatically dispose submitted textures when packing
|
||||||
|
- Added JsonTypeSafeWrapper and JsonTypeSafeGenericDataHolder
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
- Use TitleContainer for opening streams where possible
|
- Use TitleContainer for opening streams where possible
|
||||||
|
|
45
MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs
Normal file
45
MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using MLEM.Misc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MLEM.Data.Json {
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IGenericDataHolder"/> represents an object that can hold generic key-value based data.
|
||||||
|
/// This class uses <see cref="JsonTypeSafeWrapper"/> for each object stored to ensure that objects with a custom <see cref="JsonConverter"/> get deserialized as an instance of their original type if <see cref="JsonSerializer.TypeNameHandling"/> is not set to <see cref="TypeNameHandling.None"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataContract]
|
||||||
|
public class JsonTypeSafeGenericDataHolder : IGenericDataHolder {
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
private Dictionary<string, JsonTypeSafeWrapper> data;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SetData<T>(string key, T data) {
|
||||||
|
if (EqualityComparer<T>.Default.Equals(data, default)) {
|
||||||
|
if (this.data != null)
|
||||||
|
this.data.Remove(key);
|
||||||
|
} else {
|
||||||
|
if (this.data == null)
|
||||||
|
this.data = new Dictionary<string, JsonTypeSafeWrapper>();
|
||||||
|
this.data[key] = new JsonTypeSafeWrapper<T>(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public T GetData<T>(string key) {
|
||||||
|
if (this.data != null && this.data.TryGetValue(key, out var val))
|
||||||
|
return (T) val.Value;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<string> GetDataKeys() {
|
||||||
|
if (this.data == null)
|
||||||
|
return Array.Empty<string>();
|
||||||
|
return this.data.Keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
40
MLEM.Data/Json/JsonTypeSafeWrapper.cs
Normal file
40
MLEM.Data/Json/JsonTypeSafeWrapper.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MLEM.Data.Json {
|
||||||
|
/// <summary>
|
||||||
|
/// A json type-safe wrapper can be used to wrap any objects of any type before submitting them to a non-specifically typed <see cref="List{T}"/> or <see cref="Dictionary{T,T}"/> that will be serialized using a <see cref="JsonSerializer"/> in cases where <see cref="JsonSerializer.TypeNameHandling"/> is not set to <see cref="TypeNameHandling.None"/>.
|
||||||
|
/// If any object is not wrapped in this manner, and it has a custom <see cref="JsonConverter"/>, 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 <see cref="List{T}"/> of <see cref="object"/> entries, one of which is a <see cref="TimeSpan"/>: The <see cref="TimeSpan"/> will be serialized as a <see cref="string"/> and, upon deserialization, will remain a <see cref="string"/>.
|
||||||
|
/// 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 <see cref="JsonTypeSafeGenericDataHolder"/> for an example of how this class and <see cref="JsonTypeSafeWrapper{T}"/> can be used, and see this stackoverflow answer for more information on the problem that this class solves: https://stackoverflow.com/a/38798114.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class JsonTypeSafeWrapper {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns this json type-safe wrapper's value as an <see cref="object"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract object Value { get; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DataContract]
|
||||||
|
public class JsonTypeSafeWrapper<T> : JsonTypeSafeWrapper {
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override object Value => this.value;
|
||||||
|
[DataMember]
|
||||||
|
private readonly T value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new json type-safe wrapper instance that wraps the given <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to wrap</param>
|
||||||
|
public JsonTypeSafeWrapper(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ namespace MLEM.Ui.Style {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The style settings for a <see cref="UiSystem"/>.
|
/// The style settings for a <see cref="UiSystem"/>.
|
||||||
/// Each <see cref="Element"/> uses these style settings by default, however you can also change these settings per element using the elements' individual style settings.
|
/// Each <see cref="Element"/> 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 <see cref="GenericDataHolder"/>, meaning additional styles for custom components can easily be added using <see cref="GenericDataHolder.SetData"/>
|
/// Note that this class is a <see cref="GenericDataHolder"/>, meaning additional styles for custom components can easily be added using <see cref="GenericDataHolder.SetData{T}"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UiStyle : GenericDataHolder {
|
public class UiStyle : GenericDataHolder {
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ namespace MLEM.Misc {
|
||||||
private Dictionary<string, object> data;
|
private Dictionary<string, object> data;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SetData(string key, object data) {
|
public void SetData<T>(string key, T data) {
|
||||||
if (data == default) {
|
if (EqualityComparer<T>.Default.Equals(data, default)) {
|
||||||
if (this.data != null)
|
if (this.data != null)
|
||||||
this.data.Remove(key);
|
this.data.Remove(key);
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,7 +30,7 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<string> GetDataKeys() {
|
public IEnumerable<string> GetDataKeys() {
|
||||||
if (this.data == null)
|
if (this.data == null)
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
return this.data.Keys;
|
return this.data.Keys;
|
||||||
|
@ -49,7 +49,8 @@ namespace MLEM.Misc {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The key to store the data by</param>
|
/// <param name="key">The key to store the data by</param>
|
||||||
/// <param name="data">The data to store in the object</param>
|
/// <param name="data">The data to store in the object</param>
|
||||||
void SetData(string key, object data);
|
/// <typeparam name="T">The type of the data to store</typeparam>
|
||||||
|
void SetData<T>(string key, T data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a piece of generic data of the given type on this object.
|
/// 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.
|
/// Returns all of the generic data that this object stores.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The generic data on this object</returns>
|
/// <returns>The generic data on this object</returns>
|
||||||
IReadOnlyCollection<string> GetDataKeys();
|
IEnumerable<string> GetDataKeys();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -92,6 +92,31 @@ namespace Tests {
|
||||||
Assert.AreEqual(Or(flags[24], flags[43]).ToString(), "Flag24 | Flag43");
|
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<TimeSpan>(read.GetData<object>("Time"));
|
||||||
|
Assert.Throws<InvalidCastException>(() => read.GetData<TimeSpan>("Time"));
|
||||||
|
|
||||||
|
// json type safe generic data holder should wrap the time span to ensure that it stays a time span
|
||||||
|
var safeData = new JsonTypeSafeGenericDataHolder();
|
||||||
|
safeData.SetData("Time", TimeSpan.FromMinutes(5));
|
||||||
|
var safeRead = SerializeAndDeserialize(serializer, safeData);
|
||||||
|
Assert.IsInstanceOf<TimeSpan>(safeRead.GetData<object>("Time"));
|
||||||
|
Assert.DoesNotThrow(() => safeRead.GetData<TimeSpan>("Time"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T SerializeAndDeserialize<T>(JsonSerializer serializer, T t) {
|
||||||
|
var writer = new StringWriter();
|
||||||
|
serializer.Serialize(writer, t);
|
||||||
|
return serializer.Deserialize<T>(new JsonTextReader(new StringReader(writer.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
private class TestObject {
|
private class TestObject {
|
||||||
|
|
||||||
public Vector2 Vec;
|
public Vector2 Vec;
|
||||||
|
|
Loading…
Reference in a new issue