Replace Newtonsoft.Json with System.Text.Json (#869)

* Converted to use `System.Text.Json`

* Remove Newtonsoft.Json Dependency
Newtonsoft.Json dependency has been removed in favor of System.Text.Json

* Treat MGFXO file as binary
This commit is contained in:
Christopher Whitley
2024-05-22 23:23:36 -04:00
committed by GitHub
parent ee0883ebb4
commit d008b1bd41
57 changed files with 1120 additions and 919 deletions
+7 -3
View File
@@ -30,10 +30,8 @@
*.sln text eol=crlf merge=union
################################################################################
### imageln file behavior
### image file behavior
### - Treat as binary
### - Normalize to Windows-style line endings
### - Use a union merge when resolving conflicts
################################################################################
*.bmp binary
*.gif binary
@@ -42,3 +40,9 @@
*.jpeg binary
*.png binary
*.webp binary
################################################################################
### MGFXO file behavior
### - Treat as binary
################################################################################
*.mgfxo binary
@@ -1,6 +1,6 @@
using System.IO;
using System.Text.Json;
using Microsoft.Xna.Framework.Content.Pipeline;
using Newtonsoft.Json;
namespace MonoGame.Extended.Content.Pipeline.Animations
{
@@ -10,15 +10,9 @@ namespace MonoGame.Extended.Content.Pipeline.Animations
{
public override ContentImporterResult<AstridAnimatorFile> Import(string filename, ContentImporterContext context)
{
using (var streamReader = new StreamReader(filename))
{
using (var jsonReader = new JsonTextReader(streamReader))
{
var serializer = new JsonSerializer();
var data = serializer.Deserialize<AstridAnimatorFile>(jsonReader);
return new ContentImporterResult<AstridAnimatorFile>(filename, data);
}
}
var json = File.ReadAllText(filename);
var data = JsonSerializer.Deserialize<AstridAnimatorFile>(json);
return new ContentImporterResult<AstridAnimatorFile>(filename, data);
}
}
}
}
@@ -15,9 +15,6 @@
<PackageReference Include="MonoGame.Framework.Content.Pipeline"
Version="3.8.1.303"
PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json"
Version="13.0.1" />
</ItemGroup>
<ItemGroup>
@@ -25,10 +22,4 @@
<ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Newtonsoft.Json.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -1,7 +1,8 @@
using System.IO;
using System.Text.Json;
using Microsoft.Xna.Framework.Content.Pipeline;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Content.Pipeline.TextureAtlases
{
@@ -10,14 +11,8 @@ namespace MonoGame.Extended.Content.Pipeline.TextureAtlases
{
public override TexturePackerFile Import(string filename, ContentImporterContext context)
{
using (var streamReader = new StreamReader(filename))
{
using (var jsonReader = new JsonTextReader(streamReader))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<TexturePackerFile>(jsonReader);
}
}
var json = File.ReadAllText(filename);
return JsonSerializer.Deserialize<TexturePackerFile>(json);
}
}
}
}
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Input.InputListeners;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Controls
{
@@ -98,7 +98,7 @@ namespace MonoGame.Extended.Gui.Controls
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Stretch;
public HorizontalAlignment HorizontalTextAlignment { get; set; } = HorizontalAlignment.Centre;
public VerticalAlignment VerticalTextAlignment { get; set; } = VerticalAlignment.Centre;
public abstract Size GetContentSize(IGuiContext context);
public virtual Size CalculateActualSize(IGuiContext context)
@@ -177,7 +177,7 @@ namespace MonoGame.Extended.Gui.Controls
public virtual bool OnPointerDown(IGuiContext context, PointerEventArgs args) { return true; }
public virtual bool OnPointerMove(IGuiContext context, PointerEventArgs args) { return true; }
public virtual bool OnPointerUp(IGuiContext context, PointerEventArgs args) { return true; }
public virtual bool OnPointerEnter(IGuiContext context, PointerEventArgs args)
{
if (IsEnabled && !IsHovered)
@@ -220,7 +220,7 @@ namespace MonoGame.Extended.Gui.Controls
{
return Parent != null && (Parent == control || Parent.HasParent(control));
}
protected TextInfo GetTextInfo(IGuiContext context, string text, Rectangle targetRectangle, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment)
{
var font = Font ?? context.DefaultFont;
@@ -230,7 +230,7 @@ namespace MonoGame.Extended.Gui.Controls
var textInfo = new TextInfo(text, font, textPosition, textSize, TextColor, targetRectangle);
return textInfo;
}
public struct TextInfo
{
public TextInfo(string text, BitmapFont font, Vector2 position, Size size, Color color, Rectangle? clippingRectangle)
@@ -268,4 +268,4 @@ namespace MonoGame.Extended.Gui.Controls
return null;
}
}
}
}
+1 -1
View File
@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui
{
+9 -19
View File
@@ -1,11 +1,12 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.Gui.Controls;
using MonoGame.Extended.Gui.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui
{
@@ -21,7 +22,7 @@ namespace MonoGame.Extended.Gui
}
private Control _content;
[JsonProperty(Order = 1)]
[JsonPropertyOrder(1)]
public Control Content
{
get { return _content; }
@@ -49,7 +50,7 @@ namespace MonoGame.Extended.Gui
public virtual void Update(GameTime gameTime)
{
}
public void Show()
@@ -113,21 +114,10 @@ namespace MonoGame.Extended.Gui
where TScreen : Screen
{
var skinService = new SkinService();
var serializer = new GuiJsonSerializer(contentManager, customControlTypes)
{
Converters =
{
new SkinJsonConverter(contentManager, skinService, customControlTypes),
new ControlJsonConverter(skinService, customControlTypes)
}
};
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
var screen = serializer.Deserialize<TScreen>(jsonReader);
return screen;
}
var options = GuiJsonSerializerOptionsProvider.GetOptions(contentManager, customControlTypes);
options.Converters.Add(new SkinJsonConverter(contentManager, skinService, customControlTypes));
options.Converters.Add(new ControlJsonConverter(skinService, customControlTypes));
return JsonSerializer.Deserialize<TScreen>(stream, options);
}
public static Screen FromFile(ContentManager contentManager, string path, params Type[] customControlTypes)
@@ -147,4 +137,4 @@ namespace MonoGame.Extended.Gui
}
}
}
}
}
@@ -1,48 +0,0 @@
using System;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
{
public class AlignmentConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(HorizontalAlignment))
{
var value = reader.Value.ToString();
if (value == "Center" || string.Equals(value, "Centre", StringComparison.OrdinalIgnoreCase))
return HorizontalAlignment.Centre;
HorizontalAlignment alignment;
if (Enum.TryParse(value, true, out alignment))
return alignment;
}
if (objectType == typeof(VerticalAlignment))
{
var value = reader.Value.ToString();
if (value == "Center" || string.Equals(value, "Centre", StringComparison.OrdinalIgnoreCase))
return VerticalAlignment.Centre;
VerticalAlignment alignment;
if (Enum.TryParse(value, true, out alignment))
return alignment;
}
throw new InvalidOperationException($"Invalid value for '{objectType.Name}'");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HorizontalAlignment) || objectType == typeof(VerticalAlignment);
}
}
}
@@ -1,10 +1,11 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using MonoGame.Extended.Gui.Controls;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
{
public class ControlJsonConverter : JsonConverter
public class ControlJsonConverter : JsonConverter<Control>
{
private readonly IGuiSkinService _guiSkinService;
private readonly ControlStyleJsonConverter _styleConverter;
@@ -16,13 +17,13 @@ namespace MonoGame.Extended.Gui.Serialization
_styleConverter = new ControlStyleJsonConverter(customControlTypes);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Control);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
/// <inheritdoc />
public override Control Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var style = (ControlStyle) _styleConverter.ReadJson(reader, objectType, existingValue, serializer);
var style = _styleConverter.Read(ref reader, typeToConvert, options);
var template = GetControlTemplate(style);
var skin = _guiSkinService.Skin;
var control = skin.Create(style.TargetType, template);
@@ -48,10 +49,10 @@ namespace MonoGame.Extended.Gui.Serialization
return control;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Control);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Control value, JsonSerializerOptions options) { }
private static string GetControlTemplate(ControlStyle style)
{
@@ -63,4 +64,4 @@ namespace MonoGame.Extended.Gui.Serialization
return null;
}
}
}
}
@@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using MonoGame.Extended.Collections;
using MonoGame.Extended.Gui.Controls;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
{
public class ControlStyleJsonConverter : JsonConverter
public class ControlStyleJsonConverter : JsonConverter<ControlStyle>
{
private readonly Dictionary<string, Type> _controlTypes;
private const string _typeProperty = "Type";
@@ -26,27 +27,20 @@ namespace MonoGame.Extended.Gui.Serialization
.ToDictionary(t => t.Name);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(ControlStyle);
/// <inheritdoc />
public override ControlStyle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var style = (ControlStyle)value;
var dictionary = new Dictionary<string, object> { [_typeProperty] = style.TargetType.Name };
foreach (var keyValuePair in style)
dictionary.Add(keyValuePair.Key, keyValuePair.Value);
serializer.Serialize(writer, dictionary);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dictionary = serializer.Deserialize<Dictionary<string, object>>(reader);
var dictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options);
var name = dictionary.GetValueOrDefault(_nameProperty) as string;
var typeName = dictionary.GetValueOrDefault(_typeProperty) as string;
if (!_controlTypes.ContainsKey(typeName))
if (!_controlTypes.TryGetValue(typeName, out Type controlType))
throw new FormatException("invalid control type: " + typeName);
var targetType = typeName != null ? _controlTypes[typeName] : typeof(Control);
var targetType = typeName != null ? controlType : typeof(Control);
var properties = targetType
.GetRuntimeProperties()
.ToDictionary(p => p.Name);
@@ -59,8 +53,8 @@ namespace MonoGame.Extended.Gui.Serialization
PropertyInfo propertyInfo;
var value = properties.TryGetValue(propertyName, out propertyInfo)
? DeserializeValueAs(serializer, rawValue, propertyInfo.PropertyType)
: DeserializeValueAs(serializer, rawValue, typeof(object));
? DeserializeValueAs(rawValue, propertyInfo.PropertyType)
: DeserializeValueAs(rawValue, typeof(object));
style.Add(propertyName, value);
}
@@ -68,20 +62,28 @@ namespace MonoGame.Extended.Gui.Serialization
return style;
}
private static object DeserializeValueAs(JsonSerializer serializer, object value, Type type)
private static object DeserializeValueAs(object value, Type type)
{
var json = JsonConvert.SerializeObject(value);
using (var textReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(textReader))
{
return serializer.Deserialize(jsonReader, type);
}
var json = JsonSerializer.Serialize(value, type);
return JsonSerializer.Deserialize(json, type);
}
public override bool CanConvert(Type objectType)
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, ControlStyle value, JsonSerializerOptions options)
{
return objectType == typeof(ControlStyle);
var style = (ControlStyle)value;
var dictionary = new Dictionary<string, object> { [_typeProperty] = style.TargetType.Name };
foreach (var keyValuePair in style)
dictionary.Add(keyValuePair.Key, keyValuePair.Value);
JsonSerializer.Serialize(writer, dictionary);
}
}
}
}
@@ -1,29 +0,0 @@
using System;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
{
public sealed class GuiJsonSerializer : JsonSerializer
{
public GuiJsonSerializer(ContentManager contentManager, params Type[] customControlTypes)
{
var textureRegionService = new GuiTextureRegionService();
Converters.Add(new Vector2JsonConverter());
Converters.Add(new SizeJsonConverter());
Converters.Add(new Size2JsonConverter());
Converters.Add(new ColorJsonConverter());
Converters.Add(new ThicknessJsonConverter());
Converters.Add(new ContentManagerJsonConverter<BitmapFont>(contentManager, font => font.Name));
Converters.Add(new ControlStyleJsonConverter(customControlTypes));
Converters.Add(new GuiTextureAtlasJsonConverter(contentManager, textureRegionService));
Converters.Add(new GuiNinePatchRegion2DJsonConverter(textureRegionService));
Converters.Add(new TextureRegion2DJsonConverter(textureRegionService));
Converters.Add(new AlignmentConverter());
ContractResolver = new ShortNameJsonContractResolver();
Formatting = Formatting.Indented;
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.Gui.Serialization;
public static class GuiJsonSerializerOptionsProvider
{
public static JsonSerializerOptions GetOptions(ContentManager contentManager, params Type[] customControlTypes)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var textureRegionService = new GuiTextureRegionService();
options.Converters.Add(new Vector2JsonConverter());
options.Converters.Add(new SizeJsonConverter());
options.Converters.Add(new Size2JsonConverter());
options.Converters.Add(new ColorJsonConverter());
options.Converters.Add(new ThicknessJsonConverter());
options.Converters.Add(new ContentManagerJsonConverter<BitmapFont>(contentManager, font => font.Name));
options.Converters.Add(new ControlStyleJsonConverter(customControlTypes));
options.Converters.Add(new GuiTextureAtlasJsonConverter(contentManager, textureRegionService));
options.Converters.Add(new GuiNinePatchRegion2DJsonConverter(textureRegionService));
options.Converters.Add(new TextureRegion2DJsonConverter(textureRegionService));
options.Converters.Add(new VerticalAlignmentConverter());
options.Converters.Add(new HorizontalAlignmentConverter());
return options;
}
}
@@ -1,7 +1,4 @@
using System;
using MonoGame.Extended.Serialization;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
{
@@ -14,15 +11,5 @@ namespace MonoGame.Extended.Gui.Serialization
{
_textureRegionService = textureRegionService;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var ninePatch = base.ReadJson(reader, objectType, existingValue, serializer) as NinePatchRegion2D;
if(ninePatch != null)
_textureRegionService.NinePatches.Add(ninePatch);
return ninePatch;
}
}
}
}
@@ -1,8 +1,8 @@
using System;
using System.Text.Json;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.Serialization;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
{
@@ -16,14 +16,16 @@ namespace MonoGame.Extended.Gui.Serialization
_textureRegionService = textureRegionService;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
/// <inheritdoc />
public override TextureAtlas Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var textureAtlas = base.ReadJson(reader, objectType, existingValue, serializer) as TextureAtlas;
if (textureAtlas != null)
var textureAtlas = base.Read(ref reader, typeToConvert, options);
if (textureAtlas is not null)
{
_textureRegionService.TextureAtlases.Add(textureAtlas);
}
return textureAtlas;
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Gui.Serialization;
public class HorizontalAlignmentConverter : JsonConverter<HorizontalAlignment>
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(HorizontalAlignment);
/// <inheritdoc />
public override HorizontalAlignment Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
if (value.Equals("Center", StringComparison.OrdinalIgnoreCase) || value.Equals("Centre", StringComparison.OrdinalIgnoreCase))
{
return HorizontalAlignment.Centre;
}
if (Enum.TryParse<HorizontalAlignment>(value, true, out var alignment))
{
return alignment;
}
throw new InvalidOperationException($"Invalid value for '{nameof(HorizontalAlignment)}'");
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, HorizontalAlignment value, JsonSerializerOptions options) { }
}
@@ -1,58 +1,57 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Serialization
namespace MonoGame.Extended.Gui.Serialization;
public interface IGuiSkinService
{
public interface IGuiSkinService
Skin Skin { get; set; }
}
public class SkinService : IGuiSkinService
{
public Skin Skin { get; set; }
}
public class SkinJsonConverter : JsonConverter<Skin>
{
private readonly ContentManager _contentManager;
private readonly IGuiSkinService _skinService;
private readonly Type[] _customControlTypes;
public SkinJsonConverter(ContentManager contentManager, IGuiSkinService skinService, params Type[] customControlTypes)
{
Skin Skin { get; set; }
_contentManager = contentManager;
_skinService = skinService;
_customControlTypes = customControlTypes;
}
public class SkinService : IGuiSkinService
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Skin);
/// <inheritdoc />
public override Skin Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public Skin Skin { get; set; }
}
public class SkinJsonConverter : JsonConverter
{
private readonly ContentManager _contentManager;
private readonly IGuiSkinService _skinService;
private readonly Type[] _customControlTypes;
public SkinJsonConverter(ContentManager contentManager, IGuiSkinService skinService, params Type[] customControlTypes)
if (reader.TokenType == JsonTokenType.String)
{
_contentManager = contentManager;
_skinService = skinService;
_customControlTypes = customControlTypes;
}
var assetName = reader.GetString();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.ValueType == typeof(string))
// TODO: Load this using the ContentManager instead.
using (var stream = TitleContainer.OpenStream(assetName))
{
var assetName = (string) reader.Value;
// TODO: Load this using the ContentManager instead.
using (var stream = TitleContainer.OpenStream(assetName))
{
var skin = Skin.FromStream(_contentManager, stream, _customControlTypes);
_skinService.Skin = skin;
return skin;
}
var skin = Skin.FromStream(_contentManager, stream, _customControlTypes);
_skinService.Skin = skin;
return skin;
}
throw new InvalidOperationException($"{nameof(SkinJsonConverter)} can only convert from a string");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Skin);
}
throw new InvalidOperationException($"{nameof(SkinJsonConverter)} can only convert from a string");
}
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Skin value, JsonSerializerOptions options) { }
}
@@ -0,0 +1,32 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Gui.Serialization;
public class VerticalAlignmentConverter : JsonConverter<VerticalAlignment>
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(VerticalAlignment);
/// <inheritdoc />
public override VerticalAlignment Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
if (value.Equals("Center", StringComparison.OrdinalIgnoreCase) || value.Equals("Centre", StringComparison.OrdinalIgnoreCase))
{
return VerticalAlignment.Centre;
}
if (Enum.TryParse<VerticalAlignment>(value, true, out var alignment))
{
return alignment;
}
throw new InvalidOperationException($"Invalid value for '{nameof(VerticalAlignment)}'");
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, VerticalAlignment value, JsonSerializerOptions options) { }
}
+13 -17
View File
@@ -10,7 +10,8 @@ using MonoGame.Extended.Collections;
using MonoGame.Extended.Gui.Controls;
using MonoGame.Extended.Gui.Serialization;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
using System.Text.Json;
namespace MonoGame.Extended.Gui
{
@@ -24,25 +25,25 @@ namespace MonoGame.Extended.Gui
Styles = new KeyedCollection<string, ControlStyle>(s => s.Name ?? s.TargetType.Name);
}
[JsonProperty(Order = 0)]
[JsonPropertyOrder(0)]
public string Name { get; set; }
[JsonProperty(Order = 1)]
[JsonPropertyOrder(1)]
public IList<TextureAtlas> TextureAtlases { get; set; }
[JsonProperty(Order = 2)]
[JsonPropertyOrder(2)]
public IList<BitmapFont> Fonts { get; set; }
[JsonProperty(Order = 3)]
[JsonPropertyOrder(3)]
public IList<NinePatchRegion2D> NinePatches { get; set; }
[JsonProperty(Order = 4)]
[JsonPropertyOrder(4)]
public BitmapFont DefaultFont => Fonts.FirstOrDefault();
[JsonProperty(Order = 5)]
[JsonPropertyOrder(5)]
public Cursor Cursor { get; set; }
[JsonProperty(Order = 6)]
[JsonPropertyOrder(6)]
public KeyedCollection<string, ControlStyle> Styles { get; private set; }
public ControlStyle GetStyle(string name)
@@ -87,13 +88,8 @@ namespace MonoGame.Extended.Gui
public static Skin FromStream(ContentManager contentManager, Stream stream, params Type[] customControlTypes)
{
var skinSerializer = new GuiJsonSerializer(contentManager, customControlTypes);
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
return skinSerializer.Deserialize<Skin>(jsonReader);
}
var options = GuiJsonSerializerOptionsProvider.GetOptions(contentManager, customControlTypes);
return JsonSerializer.Deserialize<Skin>(stream, options);
}
@@ -139,7 +135,7 @@ namespace MonoGame.Extended.Gui
{nameof(Control.Padding), new Thickness(5)},
{nameof(Control.DisabledStyle), new ControlStyle(typeof(Control)) {
{ nameof(Control.TextColor), new Color(78,78,80) }
}
}
}
},
new ControlStyle(typeof(LayoutControl)) {
@@ -222,4 +218,4 @@ namespace MonoGame.Extended.Gui
return Default;
}
}
}
}
@@ -2,10 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Particles.Serialization;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.Particles
{
@@ -48,13 +48,8 @@ namespace MonoGame.Extended.Particles
public static ParticleEffect FromStream(ITextureRegionService textureRegionService, Stream stream)
{
var serializer = new ParticleJsonSerializer(textureRegionService);
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
return serializer.Deserialize<ParticleEffect>(jsonReader);
}
var options = ParticleJsonSerializerOptionsProvider.GetOptions(textureRegionService);
return JsonSerializer.Deserialize<ParticleEffect>(stream, options);
}
public void Update(float elapsedSeconds)
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Particles.Modifiers;
using MonoGame.Extended.Particles.Profiles;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Particles
{
@@ -42,7 +42,7 @@ namespace MonoGame.Extended.Particles
Buffer.Dispose();
GC.SuppressFinalize(this);
}
~ParticleEmitter()
{
Dispose();
@@ -197,7 +197,7 @@ namespace MonoGame.Extended.Particles
_random.NextColor(out particle->Color, Parameters.Color);
particle->Opacity = _random.NextSingle(Parameters.Opacity);
if(Parameters.MaintainAspectRatioOnScale)
{
var scale = _random.NextSingle(Parameters.Scale);
@@ -207,7 +207,7 @@ namespace MonoGame.Extended.Particles
{
particle->Scale = new Vector2(_random.NextSingle(Parameters.ScaleX), _random.NextSingle(Parameters.ScaleY));
}
particle->Rotation = _random.NextSingle(Parameters.Rotation);
particle->Mass = _random.NextSingle(Parameters.Mass);
particle->LayerDepth = layerDepth;
@@ -1,27 +1,34 @@
using System;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Particles.Serialization
namespace MonoGame.Extended.Particles.Serialization;
/// <summary>
/// Converts a <see cref="ParticleModifierExecutionStrategy"/> value to or from JSON.
/// </summary>
public class ModifierExecutionStrategyJsonConverter : JsonConverter<ParticleModifierExecutionStrategy>
{
public class ModifierExecutionStrategyJsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) =>
typeToConvert == typeof(ParticleModifierExecutionStrategy) ||
typeToConvert.GetTypeInfo().BaseType == typeof(ParticleModifierExecutionStrategy);
/// <inheritdoc />
public override ParticleModifierExecutionStrategy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = JToken.Load(reader).ToObject<string>();
return ParticleModifierExecutionStrategy.Parse(value);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ParticleModifierExecutionStrategy)
|| objectType.GetTypeInfo().BaseType == typeof(ParticleModifierExecutionStrategy);
}
var value = JsonSerializer.Deserialize<string>(ref reader, options);
return ParticleModifierExecutionStrategy.Parse(value);
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, ParticleModifierExecutionStrategy value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue(value.ToString());
}
}
@@ -1,28 +0,0 @@
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.Particles.Serialization
{
public sealed class ParticleJsonSerializer : JsonSerializer
{
public ParticleJsonSerializer(ITextureRegionService textureRegionService, NullValueHandling nullValueHandling = NullValueHandling.Include)
{
Converters.Add(new Vector2JsonConverter());
Converters.Add(new Size2JsonConverter());
Converters.Add(new ColorJsonConverter());
Converters.Add(new TextureRegion2DJsonConverter(textureRegionService));
Converters.Add(new ProfileJsonConverter());
Converters.Add(new ModifierJsonConverter());
Converters.Add(new InterpolatorJsonConverter());
Converters.Add(new TimeSpanJsonConverter());
Converters.Add(new RangeJsonConverter<int>());
Converters.Add(new RangeJsonConverter<float>());
Converters.Add(new RangeJsonConverter<HslColor>());
Converters.Add(new HslColorJsonConverter());
Converters.Add(new ModifierExecutionStrategyJsonConverter());
ContractResolver = new ShortNameJsonContractResolver();
NullValueHandling = nullValueHandling;
Formatting = Formatting.Indented;
}
}
}
@@ -0,0 +1,34 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.Particles.Serialization;
public static class ParticleJsonSerializerOptionsProvider
{
public static JsonSerializerOptions GetOptions(ITextureRegionService textureRegionService)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
options.Converters.Add(new Vector2JsonConverter());
options.Converters.Add(new Size2JsonConverter());
options.Converters.Add(new ColorJsonConverter());
options.Converters.Add(new TextureRegion2DJsonConverter(textureRegionService));
options.Converters.Add(new ProfileJsonConverter());
options.Converters.Add(new ModifierJsonConverter());
options.Converters.Add(new InterpolatorJsonConverter());
options.Converters.Add(new TimeSpanJsonConverter());
options.Converters.Add(new RangeJsonConverter<int>());
options.Converters.Add(new RangeJsonConverter<float>());
options.Converters.Add(new RangeJsonConverter<HslColor>());
options.Converters.Add(new HslColorJsonConverter());
options.Converters.Add(new ModifierExecutionStrategyJsonConverter());
return options;
}
}
@@ -1,30 +1,33 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Particles.Serialization
namespace MonoGame.Extended.Particles.Serialization;
public class TimeSpanJsonConverter : JsonConverter<TimeSpan>
{
public class TimeSpanJsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TimeSpan);
/// <inheritdoc />
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
if (reader.TokenType == JsonTokenType.Number)
{
var timeSpan = (TimeSpan) value;
writer.WriteValue(timeSpan.TotalSeconds);
double seconds = reader.GetDouble();
return TimeSpan.FromSeconds(seconds);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.ValueType == typeof(double))
{
var seconds = (double) reader.Value;
return TimeSpan.FromSeconds(seconds);
}
return TimeSpan.Zero;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TimeSpan);
}
return TimeSpan.Zero;
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteNumberValue(value.TotalSeconds);
}
}
@@ -9,7 +9,6 @@
<PackageReference Include="MonoGame.Framework.Content.Pipeline"
Version="3.8.1.303"
PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
@@ -2,18 +2,18 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization
{
public abstract class BaseTypeJsonConverter<T> : JsonConverter
public abstract class BaseTypeJsonConverter<T> : JsonConverter<T>
{
private readonly string _suffix;
private readonly Dictionary<string, Type> _namesToTypes;
private readonly Dictionary<Type, string> _typesToNames;
private readonly CamelCaseNamingStrategy _namingStrategy = new CamelCaseNamingStrategy();
private readonly JsonSerializerOptions _serializerOptions;
private readonly JsonNamingPolicy _namingPolicy = JsonNamingPolicy.CamelCase;
protected BaseTypeJsonConverter(IEnumerable<TypeInfo> supportedTypes, string suffix)
{
@@ -21,52 +21,60 @@ namespace MonoGame.Extended.Serialization
_namesToTypes = supportedTypes
.ToDictionary(t => TrimSuffix(t.Name, suffix), t => t.AsType(), StringComparer.OrdinalIgnoreCase);
_typesToNames = _namesToTypes.ToDictionary(i => i.Value, i => i.Key);
_serializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = _namingPolicy,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
_serializerOptions.Converters.Add(this);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) =>
_namesToTypes.ContainsValue(typeToConvert) || typeof(T) == typeToConvert;
/// <inheritdoc />
/// <exception cref="InvalidOperationException" />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument doc = JsonDocument.ParseValue(ref reader))
{
var jObject = doc.RootElement;
var key = jObject.GetProperty("type").GetString();
if (_namesToTypes.TryGetValue(key, out Type type))
{
var value = JsonSerializer.Deserialize(jObject.GetRawText(), type, options);
return (T)value;
}
throw new InvalidOperationException($"Unknown {_suffix} type '{key}'");
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var type = value.GetType();
var properties = type.GetRuntimeProperties();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
writer.WriteStartObject();
writer.WritePropertyName("type");
writer.WriteValue(_typesToNames[type]);
writer.WriteString("type", _typesToNames[type]);
foreach (var property in properties.Where(p => p.CanWrite)) // TODO: Technically an IList property is writable as well.
foreach (var property in properties)
{
var propertyName = _namingStrategy.GetPropertyName(property.Name, false);
var propertyName = _namingPolicy.ConvertName(property.Name);
writer.WritePropertyName(propertyName);
serializer.Serialize(writer, property.GetValue(value));
JsonSerializer.Serialize(writer, property.GetValue(value), property.PropertyType, options);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var key = jObject.GetValue("type", StringComparison.OrdinalIgnoreCase).ToObject<string>();
Type type;
if (_namesToTypes.TryGetValue(key, out type))
{
serializer.Converters.Remove(this);
var value = jObject.ToObject(type, serializer);
serializer.Converters.Add(this);
return value;
}
throw new InvalidOperationException($"Unknown {_suffix} type '{key}'");
}
public override bool CanConvert(Type objectType)
{
if (_namesToTypes.ContainsValue(objectType))
return true;
return objectType == typeof(T);
}
private static string TrimSuffix(string input, string suffix)
{
if (input.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
@@ -75,4 +83,4 @@ namespace MonoGame.Extended.Serialization
return input;
}
}
}
}
@@ -1,25 +1,34 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="Color"/> value to or from JSON.
/// </summary>
public class ColorJsonConverter : JsonConverter<Color>
{
public class ColorJsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Color);
/// <inheritdoc />
public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(ColorHelper.ToHex((Color)value));
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Color);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = (string)reader.Value;
return value.StartsWith("#") ? ColorHelper.FromHex(value) : ColorHelper.FromName(value);
}
var value = reader.GetString();
return value[0] == '#' ? ColorHelper.FromHex(value) : ColorHelper.FromName(value);
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
var hexValue = ColorHelper.ToHex(value);
writer.WriteStringValue(hexValue);
}
}
@@ -1,40 +1,49 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework.Content;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Loads content from a JSON file into the <see cref="ContentManager"/> using the asset name
/// </summary>
/// <typeparam name="T">The type of content to load</typeparam>
public class ContentManagerJsonConverter<T> : JsonConverter<T>
{
private readonly ContentManager _contentManager;
private readonly Func<T, string> _getAssetName;
/// <summary>
/// Loads content from a JSON file into the <see cref="ContentManager"/> using the asset name
/// Initializes a new instance of the <see cref="ContentManagerJsonConverter{T}"/> class.
/// </summary>
/// <typeparam name="T">The type of content to load</typeparam>
public class ContentManagerJsonConverter<T> : JsonConverter
/// <param name="contentManager">The <see cref="ContentManager"/> used to load content.</param>
/// <param name="getAssetName">A function that returns the asset name for a given instance of <typeparamref name="T"/>.</param>
public ContentManagerJsonConverter(ContentManager contentManager, Func<T, string> getAssetName)
{
private readonly ContentManager _contentManager;
private readonly Func<T, string> _getAssetName;
public ContentManagerJsonConverter(ContentManager contentManager, Func<T, string> getAssetName)
{
_contentManager = contentManager;
_getAssetName = getAssetName;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var asset = (T)value;
var assetName = _getAssetName(asset);
writer.WriteValue(assetName);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var assetName = (string)reader.Value;
return _contentManager.Load<T>(assetName);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
_contentManager = contentManager;
_getAssetName = getAssetName;
}
}
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(T);
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var assetName = reader.GetString();
return _contentManager.Load<T>(assetName);
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
var asset = (T)value;
var assetName = _getAssetName(asset);
writer.WriteStringValue(assetName);
}
}
@@ -0,0 +1,34 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization;
public class FloatStringConverter : JsonConverter<float>
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(float) || typeToConvert == typeof(string);
/// <inheritdoc />
public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
if (float.TryParse(reader.GetString(), out float value))
return value;
}
else if (reader.TokenType == JsonTokenType.Number)
{
return reader.GetSingle();
}
throw new JsonException($"Unable to convert value of type {reader.TokenType} to {typeof(float)}");
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteNumberValue(value);
}
}
@@ -1,28 +1,33 @@
using System;
using Microsoft.Xna.Framework;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="HslColor"/> value to or from JSON.
/// </summary>
public class HslColorJsonConverter : JsonConverter<HslColor>
{
public class HslColorJsonConverter : JsonConverter
private readonly ColorJsonConverter _colorConverter = new ColorJsonConverter();
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(HslColor);
/// <inheritdoc />
public override HslColor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
private readonly ColorJsonConverter _colorConverter = new ColorJsonConverter();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var color = ((HslColor) value).ToRgb();
_colorConverter.WriteJson(writer, color, serializer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var color = (Color)_colorConverter.ReadJson(reader, objectType, existingValue, serializer);
return HslColor.FromRgb(color);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HslColor);
}
var color = _colorConverter.Read(ref reader, typeToConvert, options);
return HslColor.FromRgb(color);
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, HslColor value, JsonSerializerOptions options)
{
var color = ((HslColor)value).ToRgb();
_colorConverter.Write(writer, color, options);
}
}
@@ -1,7 +1,7 @@
using System.IO;
using System.Text.Json;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.Content;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
{
@@ -9,13 +9,10 @@ namespace MonoGame.Extended.Serialization
{
public T Load<T>(ContentManager contentManager, string path)
{
using (var stream = contentManager.OpenStream(path))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
var serializer = new MonoGameJsonSerializer(contentManager, path);
return serializer.Deserialize<T>(jsonReader);
}
using var stream = contentManager.OpenStream(path);
var monoGameSerializerOptions = MonoGameJsonSerializerOptionsProvider.GetOptions(contentManager, path);
return JsonSerializer.Deserialize<T>(stream, monoGameSerializerOptions);
}
}
}
}
@@ -1,6 +1,7 @@
using System.IO;
using System.Text.Json;
using Microsoft.Xna.Framework.Content;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
{
@@ -9,13 +10,7 @@ namespace MonoGame.Extended.Serialization
protected override T Read(ContentReader reader, T existingInstance)
{
var json = reader.ReadString();
using (var stringReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(stringReader))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<T>(jsonReader);
}
return JsonSerializer.Deserialize<T>(json);
}
}
}
}
@@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MonoGame.Extended.Serialization
{
public static class JsonReaderExtensions
{
private static readonly Dictionary<Type, Func<string, object>> _stringParsers = new Dictionary<Type, Func<string, object>>
{
{typeof(int), s => int.Parse(s, CultureInfo.InvariantCulture.NumberFormat)},
{typeof(float), s => float.Parse(s, CultureInfo.InvariantCulture.NumberFormat)},
{typeof(HslColor), s => ColorExtensions.FromHex(s).ToHsl() }
};
public static T[] ReadAsMultiDimensional<T>(this JsonReader reader)
{
var tokenType = reader.TokenType;
switch (tokenType)
{
case JsonToken.StartArray:
return reader.ReadAsJArray<T>();
case JsonToken.String:
return reader.ReadAsDelimitedString<T>();
case JsonToken.Integer:
case JsonToken.Float:
return reader.ReadAsSingleValue<T>();
default:
throw new NotSupportedException($"{tokenType} is not currently supported in the multi dimensional parser");
}
}
private static T[] ReadAsSingleValue<T>(this JsonReader reader)
{
return new[] { JToken.Load(reader).ToObject<T>() };
}
private static T[] ReadAsJArray<T>(this JsonReader reader)
{
var jArray = JArray.Load(reader);
var items = new List<T>();
foreach (var token in jArray)
{
if (token.Type == JTokenType.String)
{
var stringParser = _stringParsers[typeof(T)];
var s = token.Value<string>();
items.Add((T)stringParser(s));
}
else
{
items.Add(token.Value<T>());
}
}
return items.ToArray();
}
private static T[] ReadAsDelimitedString<T>(this JsonReader reader)
{
var value = (string)reader.Value;
var parser = _stringParsers[typeof(T)];
return value.Split(' ')
.Select(i => parser(i))
.Cast<T>()
.ToArray();
}
}
}
@@ -1,26 +0,0 @@
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
{
public sealed class MonoGameJsonSerializer : JsonSerializer
{
public MonoGameJsonSerializer(ContentManager contentManager, string contentPath, NullValueHandling nullValueHandling = NullValueHandling.Include)
{
Converters.Add(new ColorJsonConverter());
Converters.Add(new HslColorJsonConverter());
Converters.Add(new RangeJsonConverter<int>());
Converters.Add(new RangeJsonConverter<float>());
Converters.Add(new RangeJsonConverter<HslColor>());
Converters.Add(new ThicknessJsonConverter());
Converters.Add(new RectangleFJsonConverter());
Converters.Add(new TextureAtlasJsonConverter(contentManager, contentPath));
Converters.Add(new Size2JsonConverter());
ContractResolver = new ShortNameJsonContractResolver();
NullValueHandling = nullValueHandling;
Formatting = Formatting.Indented;
}
}
}
@@ -0,0 +1,29 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Serialization;
public static class MonoGameJsonSerializerOptionsProvider
{
public static JsonSerializerOptions GetOptions(ContentManager contentManager, string contentPath)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
options.Converters.Add(new RangeJsonConverter<int>());
options.Converters.Add(new RangeJsonConverter<float>());
options.Converters.Add(new RangeJsonConverter<HslColor>());
options.Converters.Add(new ThicknessJsonConverter());
options.Converters.Add(new RectangleFJsonConverter());
options.Converters.Add(new TextureAtlasJsonConverter(contentManager, contentPath));
options.Converters.Add(new Size2JsonConverter());
return options;
}
}
@@ -1,44 +1,94 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="NinePatchRegion2D"/> value to or from JSON.
/// </summary>
public class NinePatchRegion2DJsonConverter : JsonConverter<NinePatchRegion2D>
{
public class NinePatchRegion2DJsonConverter : JsonConverter
private readonly ITextureRegionService _textureRegionService;
/// <summary>
/// Initializes a new instance of the <see cref="NinePatchRegion2DJsonConverter"/> class.
/// </summary>
/// <param name="textureRegionService">The texture region service used to retrieve texture regions.</param>
public NinePatchRegion2DJsonConverter(ITextureRegionService textureRegionService)
{
private readonly ITextureRegionService _textureRegionService;
public NinePatchRegion2DJsonConverter(ITextureRegionService textureRegionService)
{
_textureRegionService = textureRegionService;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var region = (NinePatchRegion2D)value;
var jsonObject = new
{
TextureRegion = region.Name,
Padding = region.Padding
};
serializer.Serialize(writer, jsonObject);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = serializer.Deserialize<JObject>(reader);
var paddingAsString = jsonObject.Value<string>("Padding");
var thickness = Thickness.Parse(paddingAsString);
var regionName = jsonObject.Value<string>("TextureRegion");
var region = _textureRegionService.GetTextureRegion(regionName);
return new NinePatchRegion2D(region, thickness.Left, thickness.Top, thickness.Right, thickness.Bottom);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(NinePatchRegion2D);
}
_textureRegionService = textureRegionService;
}
}
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(NinePatchRegion2D);
/// <inheritdoc />
/// <exception cref="JsonException">
/// Thrown if the JSON property does not contain a properly formatted <see cref="NinePatchRegion2D"/> value
/// </exception>
public override NinePatchRegion2D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException($"Expected {nameof(JsonTokenType.StartObject)} token");
}
string padding = string.Empty;
string regionName = string.Empty;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
reader.Read();
if (propertyName.Equals("Padding", StringComparison.Ordinal))
{
padding = reader.GetString();
}
else if (propertyName.Equals("TextureRegion", StringComparison.Ordinal))
{
regionName = reader.GetString();
}
}
}
if (string.IsNullOrEmpty(padding) || string.IsNullOrEmpty(regionName))
{
throw new JsonException($"Missing required properties \"Padding\" and \"TextureRegion\"");
}
var thickness = Thickness.Parse(padding);
var region = _textureRegionService.GetTextureRegion(regionName);
return new NinePatchRegion2D(region, thickness.Left, thickness.Top, thickness.Right, thickness.Bottom);
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, NinePatchRegion2D value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
if (value is null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteString("TextureRegion", value.Name);
writer.WriteString("Padding", value.Padding.ToString());
writer.WriteEndObject();
}
}
@@ -1,48 +1,50 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="Range{T}"/> value to or from JSON.
/// </summary>
public class RangeJsonConverter<T> : JsonConverter<Range<T>> where T : IComparable<T>
{
public class RangeJsonConverter<T> : JsonConverter where T : IComparable<T>
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Range<T>);
/// <inheritdoc />
public override Range<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
Span<T> values = reader.ReadAsMultiDimensional<T>();
if (values.Length == 2)
{
var range = (Range<T>) value;
var formatting = writer.Formatting;
writer.Formatting = Formatting.None;
writer.WriteWhitespace(" ");
writer.WriteStartArray();
serializer.Serialize(writer, range.Min);
serializer.Serialize(writer, range.Max);
//writer.WriteValue(range.Min);
//writer.WriteValue(range.Max);
writer.WriteEndArray();
writer.Formatting = formatting;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var values = reader.ReadAsMultiDimensional<T>();
if (values.Length == 2)
if (values[0].CompareTo(values[1]) < 0)
{
if (values[0].CompareTo(values[1]) < 0)
return new Range<T>(values[0], values[1]);
return new Range<T>(values[1], values[0]);
return new Range<T>(values[0], values[1]);
}
if (values.Length == 1)
return new Range<T>(values[0], values[0]);
throw new InvalidOperationException("Invalid range");
return new Range<T>(values[1], values[0]);
}
public override bool CanConvert(Type objectType)
if (values.Length == 1)
{
return objectType == typeof(Range<T>);
return new Range<T>(values[0], values[0]);
}
throw new InvalidOperationException("Invalid range");
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, Range<T> value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStartArray();
JsonSerializer.Serialize(writer, value.Min, options);
JsonSerializer.Serialize(writer, value.Max, options);
writer.WriteEndArray();
}
}
@@ -1,24 +1,31 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization;
public class RectangleFJsonConverter: JsonConverter
/// <summary>
/// Converts a <see cref="RectangleF"/> value to or from JSON.
/// </summary>
public class RectangleFJsonConverter : JsonConverter<RectangleF>
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var rect = (RectangleF)value;
writer.WriteValue($"{rect.Left} {rect.Top} {rect.Width} {rect.Height}");
}
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(RectangleF);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
/// <inheritdoc />
public override RectangleF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = reader.ReadAsMultiDimensional<float>();
return new RectangleF(values[0], values[1], values[2], values[3]);
}
public override bool CanConvert(Type objectType)
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, RectangleF value, JsonSerializerOptions options)
{
return objectType == typeof(RectangleF);
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue($"{value.Left} {value.Top} {value.Width} {value.Height}");
}
}
@@ -1,57 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace MonoGame.Extended.Serialization
{
public class ShortNameJsonContractResolver : CamelCasePropertyNamesContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization)
.Where(p => p.Writable || IsListProperty(p))
.ToList();
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsAbstract)
{
properties.Insert(0, new JsonProperty
{
PropertyType = typeof(string),
PropertyName = "type",
Readable = true,
Writable = false,
ValueProvider = new JsonShortTypeNameProvider()
});
}
return properties;
}
private static bool IsListProperty(JsonProperty property)
{
return typeof(IList).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo());
}
private class JsonShortTypeNameProvider : IValueProvider
{
public JsonShortTypeNameProvider()
{
}
public void SetValue(object target, object value)
{
}
public object GetValue(object target)
{
return target.GetType().Name;
}
}
}
}
@@ -1,33 +1,45 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="Size2"/> value to or from JSON.
/// </summary>
public class Size2JsonConverter : JsonConverter<Size2>
{
public class Size2JsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Size2);
/// <inheritdoc />
/// <exception cref="JsonException">
/// Thrown if the JSON property does not contain a properly formatted <see cref="Size2"/> value
/// </exception>
public override Size2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var values = reader.ReadAsMultiDimensional<float>();
if (values.Length == 2)
{
var sizeF = (Size2) value;
writer.WriteValue($"{sizeF.Width} {sizeF.Height}");
return new Size2(values[0], values[1]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
if (values.Length == 1)
{
var values = reader.ReadAsMultiDimensional<float>();
if(values.Length == 2)
return new Size2(values[0], values[1]);
if (values.Length == 1)
return new Size2(values[0], values[0]);
throw new FormatException("Invalid Size property value");
return new Size2(values[0], values[0]);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Size2);
}
throw new JsonException("Invalid Size2 property value");
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, Size2 value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue($"{value.Width} {value.Height}");
}
}
@@ -1,33 +1,45 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="Size"/> value to or from JSON.
/// </summary>
public class SizeJsonConverter : JsonConverter<Size>
{
public class SizeJsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Size);
/// <inheritdoc />
/// <exception cref="JsonException">
/// Thrown if the JSON property does not contain a properly formatted <see cref="Size"/> value
/// </exception>
public override Size Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var values = reader.ReadAsMultiDimensional<int>();
if (values.Length == 2)
{
var sizeF = (Size) value;
writer.WriteValue($"{sizeF.Width} {sizeF.Height}");
return new Size(values[0], values[1]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
if (values.Length == 1)
{
var values = reader.ReadAsMultiDimensional<int>();
if(values.Length == 2)
return new Size(values[0], values[1]);
if (values.Length == 1)
return new Size(values[0], values[0]);
throw new FormatException("Invalid Size property value");
return new Size(values[0], values[0]);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Size);
}
throw new JsonException("Invalid Size property value");
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, Size value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue($"{value.Width} {value.Height}");
}
}
@@ -1,33 +1,52 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="TextureRegion2D"/> value to or from JSON.
/// </summary>
public class TextureRegion2DJsonConverter : JsonConverter<TextureRegion2D>
{
public class TextureRegion2DJsonConverter : JsonConverter
private readonly ITextureRegionService _textureRegionService;
/// <summary>
/// Initializes a new instance of the <see cref="TextureRegion2DJsonConverter"/> class.
/// </summary>
/// <param name="textureRegionService">The texture region service to use for retrieving texture regions.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="textureRegionService"/> is <see langword="null"/>.
/// </exception>
public TextureRegion2DJsonConverter(ITextureRegionService textureRegionService)
{
private readonly ITextureRegionService _textureRegionService;
public TextureRegion2DJsonConverter(ITextureRegionService textureRegionService)
{
_textureRegionService = textureRegionService;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var region = (TextureRegion2D)value;
writer.WriteValue(region.Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var regionName = reader.Value as string;
return regionName == null ? null : _textureRegionService.GetTextureRegion(regionName);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TextureRegion2D);
}
ArgumentNullException.ThrowIfNull(textureRegionService);
_textureRegionService = textureRegionService;
}
}
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TextureRegion2D);
/// <inheritdoc />
public override TextureRegion2D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var regionName = reader.GetString();
return string.IsNullOrEmpty(regionName) ? null : _textureRegionService.GetTextureRegion(regionName);
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
///
/// -or-
///
/// Thrown if <paramref name="value"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, TextureRegion2D value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
ArgumentNullException.ThrowIfNull(value);
writer.WriteStringValue(value.Name);
}
}
@@ -1,25 +1,31 @@
using System;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="Thickness"/> value to or from JSON.
/// </summary>
public class ThicknessJsonConverter : JsonConverter<Thickness>
{
public class ThicknessJsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Thickness);
/// <inheritdoc />
public override Thickness Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var thickness = (Thickness)value;
writer.WriteValue($"{thickness.Left} {thickness.Top} {thickness.Right} {thickness.Bottom}");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var values = reader.ReadAsMultiDimensional<int>();
return Thickness.FromValues(values);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Thickness);
}
var values = reader.ReadAsMultiDimensional<int>();
return Thickness.FromValues(values);
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>
/// </exception>
public override void Write(Utf8JsonWriter writer, Thickness value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue($"{value.Left} {value.Top} {value.Right} {value.Bottom}");
}
}
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Provides extension methods for working with <see cref="Utf8JsonReader"/>.
/// </summary>
public static class Utf8JsonReaderExtensions
{
private static readonly Dictionary<Type, Func<string, object>> s_stringParsers = new Dictionary<Type, Func<string, object>>
{
{typeof(int), s => int.Parse(s, CultureInfo.InvariantCulture.NumberFormat)},
{typeof(float), s => float.Parse(s, CultureInfo.InvariantCulture.NumberFormat)},
{typeof(HslColor), s => ColorExtensions.FromHex(s).ToHsl() }
};
/// <summary>
/// Reads a multi-dimensional JSON array and converts it to an array of the specified type.
/// </summary>
/// <typeparam name="T">The type of the array elements.</typeparam>
/// <param name="reader">The <see cref="Utf8JsonReader"/> to read from.</param>
/// <returns>An array of the specified type.</returns>
/// <exception cref="NotSupportedException">Thrown when the token type is not supported.</exception>
public static T[] ReadAsMultiDimensional<T>(this ref Utf8JsonReader reader)
{
var tokenType = reader.TokenType;
switch (tokenType)
{
case JsonTokenType.StartArray:
return reader.ReadAsJArray<T>();
case JsonTokenType.String:
return reader.ReadAsDelimitedString<T>();
case JsonTokenType.Number:
return reader.ReadAsSingleValue<T>();
default:
throw new NotSupportedException($"{tokenType} is not currently supported in the multi-dimensional parser");
}
}
private static T[] ReadAsSingleValue<T>(this ref Utf8JsonReader reader)
{
var token = JsonDocument.ParseValue(ref reader).RootElement;
var value = JsonSerializer.Deserialize<T>(token.GetRawText());
return new T[] { value };
}
private static T[] ReadAsJArray<T>(this ref Utf8JsonReader reader)
{
var items = new List<T>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
items.Add(JsonSerializer.Deserialize<T>(ref reader));
}
return items.ToArray();
}
private static T[] ReadAsDelimitedString<T>(this ref Utf8JsonReader reader)
{
var value = reader.GetString();
if (string.IsNullOrEmpty(value))
{
return Array.Empty<T>();
}
Span<string> values = value.Split(' ');
var result = new T[values.Length];
var parser = s_stringParsers[typeof(T)];
for (int i = 0; i < values.Length; i++)
{
result[i] = (T)parser(values[i]);
}
return result;
}
}
@@ -1,34 +1,46 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization
namespace MonoGame.Extended.Serialization;
/// <summary>
/// Converts a <see cref="Vector2"/> value to or from JSON.
/// </summary>
public class Vector2JsonConverter : JsonConverter<Vector2>
{
public class Vector2JsonConverter : JsonConverter
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Vector2);
/// <inheritdoc />
/// <exception cref="JsonException">
/// Thrown if the JSON property does not contain a properly formatted <see cref="Vector2"/> value
/// </exception>
public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var values = reader.ReadAsMultiDimensional<float>();
if (values.Length == 2)
{
var vector2 = (Vector2) value;
writer.WriteValue($"{vector2.X} {vector2.Y}");
return new Vector2(values[0], values[1]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
if (values.Length == 1)
{
var values = reader.ReadAsMultiDimensional<float>();
if(values.Length == 2)
return new Vector2(values[0], values[1]);
if (values.Length == 1)
return new Vector2(values[0]);
throw new InvalidOperationException("Invalid Vector2");
return new Vector2(values[0]);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Vector2);
}
throw new JsonException("Invalid Size2 property value");
}
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">
/// Throw if <paramref name="writer"/> is <see langword="null"/>.
/// </exception>
public override void Write(Utf8JsonWriter writer, Vector2 value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStringValue($"{value.X} {value.Y}");
}
}
@@ -1,6 +1,8 @@
using System;
using System.Diagnostics;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.Sprites
{
@@ -20,31 +22,31 @@ namespace MonoGame.Extended.Sprites
public class SpriteSheetAnimationFrameJsonConverter : JsonConverter<SpriteSheetAnimationFrame>
{
public override void WriteJson(JsonWriter writer, SpriteSheetAnimationFrame value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override SpriteSheetAnimationFrame ReadJson(JsonReader reader, Type objectType, SpriteSheetAnimationFrame existingValue, bool hasExistingValue, JsonSerializer serializer)
/// <inheritdoc />
public override SpriteSheetAnimationFrame Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonToken.Integer:
{
var index = serializer.Deserialize<int>(reader);
case JsonTokenType.Number:
var index = reader.GetInt32();
return new SpriteSheetAnimationFrame(index);
}
case JsonToken.StartObject:
{
var frame = new SpriteSheetAnimationFrame(0);
serializer.Populate(reader, frame);
case JsonTokenType.StartObject:
var frame = JsonSerializer.Deserialize<SpriteSheetAnimationFrame>(ref reader, options);
return frame;
}
case JsonToken.Null:
case JsonTokenType.Null:
return null;
default:
throw new JsonSerializationException();
throw new JsonException();
}
}
public override void Write(Utf8JsonWriter writer, SpriteSheetAnimationFrame value, JsonSerializerOptions options)
{
ArgumentNullException.ThrowIfNull(writer);
JsonSerializer.Serialize(writer, value, options);
}
}
}
}
@@ -1,9 +1,9 @@
using System.IO;
using System.Text.Json;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.Content;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
using ContentReaderExtensions = MonoGame.Extended.Content.ContentReaderExtensions;
namespace MonoGame.Extended.TextureAtlases
@@ -13,13 +13,7 @@ namespace MonoGame.Extended.TextureAtlases
private static TexturePackerFile Load(ContentReader reader)
{
var json = reader.ReadString();
using (var stringReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(stringReader))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<TexturePackerFile>(jsonReader);
}
return JsonSerializer.Deserialize<TexturePackerFile>(json);
}
protected override TextureAtlas Read(ContentReader reader, TextureAtlas existingInstance)
@@ -44,4 +38,4 @@ namespace MonoGame.Extended.TextureAtlases
return atlas;
}
}
}
}
@@ -1,14 +1,15 @@
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.Content;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.TextureAtlases
{
public class TextureAtlasJsonConverter : JsonConverter
public class TextureAtlasJsonConverter : JsonConverter<TextureAtlas>
{
private readonly ContentManager _contentManager;
private readonly string _path;
@@ -19,23 +20,16 @@ namespace MonoGame.Extended.TextureAtlases
_path = path;
}
// ReSharper disable once ClassNeverInstantiated.Local
private class InlineTextureAtlas
{
public string Texture { get; set; }
public int RegionWidth { get; set; }
public int RegionHeight { get; set; }
}
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TextureAtlas);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override TextureAtlas Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.ValueType == typeof(string))
if(reader.TokenType == JsonTokenType.String)
{
var textureAtlasAssetName = reader.Value.ToString();
// TODO: (Aristurtle 05/20/2024) What is this for? It's just an if block that throws an exception. Need
// to investigate.
var textureAtlasAssetName = reader.GetString();
var contentPath = GetContentPath(textureAtlasAssetName);
var texturePackerFile = _contentManager.Load<TexturePackerFile>(contentPath, new JsonContentLoader());
var texture = _contentManager.Load<Texture2D>(texturePackerFile.Metadata.Image);
@@ -44,7 +38,7 @@ namespace MonoGame.Extended.TextureAtlases
}
else
{
var metadata = serializer.Deserialize<InlineTextureAtlas>(reader);
var metadata = JsonSerializer.Deserialize<InlineTextureAtlas>(ref reader, options);
// TODO: When we get to .NET Standard 2.1 it would be more robust to use
// [Path.GetRelativePath](https://docs.microsoft.com/en-us/dotnet/api/system.io.path.getrelativepath?view=netstandard-2.1)
@@ -58,8 +52,9 @@ namespace MonoGame.Extended.TextureAtlases
{
texture = _contentManager.Load<Texture2D>(resolvedAssetName);
}
catch (Exception ex) {
if (textureDirectory == null || textureDirectory == "")
catch (Exception ex)
{
if (textureDirectory == null || textureDirectory == "")
texture = _contentManager.Load<Texture2D>(textureName);
else
texture = _contentManager.Load<Texture2D>(textureDirectory + "/" + textureName);
@@ -68,15 +63,22 @@ namespace MonoGame.Extended.TextureAtlases
}
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, TextureAtlas value, JsonSerializerOptions options) { }
// ReSharper disable once ClassNeverInstantiated.Local
private class InlineTextureAtlas
{
public string Texture { get; set; }
public int RegionWidth { get; set; }
public int RegionHeight { get; set; }
}
private string GetContentPath(string relativePath)
{
var directory = Path.GetDirectoryName(_path);
return Path.Combine(directory, relativePath);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TextureAtlas);
}
}
}
@@ -1,14 +1,14 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.TextureAtlases
{
public class TexturePackerFile
{
[JsonProperty("frames")]
[JsonPropertyName("frames")]
public List<TexturePackerRegion> Regions { get; set; }
[JsonProperty("meta")]
[JsonPropertyName("meta")]
public TexturePackerMeta Metadata { get; set; }
}
}
}
@@ -1,28 +1,30 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.TextureAtlases
{
public class TexturePackerMeta
{
[JsonProperty("app")]
[JsonPropertyName("app")]
public string App { get; set; }
[JsonProperty("version")]
[JsonPropertyName("version")]
public string Version { get; set; }
[JsonProperty("image")]
[JsonPropertyName("image")]
public string Image { get; set; }
[JsonProperty("format")]
[JsonPropertyName("format")]
public string Format { get; set; }
[JsonProperty("size")]
[JsonPropertyName("size")]
public TexturePackerSize Size { get; set; }
[JsonProperty("scale")]
[JsonPropertyName("scale")]
[JsonConverter(typeof(FloatStringConverter))]
public float Scale { get; set; }
[JsonProperty("smartupdate")]
[JsonPropertyName("smartupdate")]
public string SmartUpdate { get; set; }
public override string ToString()
@@ -30,4 +32,5 @@ namespace MonoGame.Extended.TextureAtlases
return Image;
}
}
}
}
@@ -1,13 +1,13 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.TextureAtlases
{
public class TexturePackerPoint
{
[JsonProperty("x")]
[JsonPropertyName("x")]
public double X { get; set; }
[JsonProperty("y")]
[JsonPropertyName("y")]
public double Y { get; set; }
public override string ToString()
@@ -15,4 +15,4 @@ namespace MonoGame.Extended.TextureAtlases
return string.Format("{0} {1}", X, Y);
}
}
}
}
@@ -1,19 +1,19 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.TextureAtlases
{
public class TexturePackerRectangle
{
[JsonProperty("x")]
[JsonPropertyName("x")]
public int X { get; set; }
[JsonProperty("y")]
[JsonPropertyName("y")]
public int Y { get; set; }
[JsonProperty("w")]
[JsonPropertyName("w")]
public int Width { get; set; }
[JsonProperty("h")]
[JsonPropertyName("h")]
public int Height { get; set; }
public override string ToString()
@@ -21,4 +21,4 @@ namespace MonoGame.Extended.TextureAtlases
return string.Format("{0} {1} {2} {3}", X, Y, Width, Height);
}
}
}
}
@@ -1,28 +1,28 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.TextureAtlases
{
public class TexturePackerRegion
{
[JsonProperty("filename")]
[JsonPropertyName("filename")]
public string Filename { get; set; }
[JsonProperty("frame")]
[JsonPropertyName("frame")]
public TexturePackerRectangle Frame { get; set; }
[JsonProperty("rotated")]
[JsonPropertyName("rotated")]
public bool IsRotated { get; set; }
[JsonProperty("trimmed")]
[JsonPropertyName("trimmed")]
public bool IsTrimmed { get; set; }
[JsonProperty("spriteSourceSize")]
[JsonPropertyName("spriteSourceSize")]
public TexturePackerRectangle SourceRectangle { get; set; }
[JsonProperty("sourceSize")]
[JsonPropertyName("sourceSize")]
public TexturePackerSize SourceSize { get; set; }
[JsonProperty("pivot")]
[JsonPropertyName("pivot")]
public TexturePackerPoint PivotPoint { get; set; }
public override string ToString()
@@ -30,4 +30,4 @@ namespace MonoGame.Extended.TextureAtlases
return $"{Filename} {Frame}";
}
}
}
}
@@ -1,13 +1,13 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace MonoGame.Extended.TextureAtlases
{
public class TexturePackerSize
{
[JsonProperty("w")]
[JsonPropertyName("w")]
public int Width { get; set; }
[JsonProperty("h")]
[JsonPropertyName("h")]
public int Height { get; set; }
public override string ToString()
@@ -15,4 +15,4 @@ namespace MonoGame.Extended.TextureAtlases
return $"{Width} {Height}";
}
}
}
}
@@ -0,0 +1,66 @@
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.Tests.Serialization;
public sealed class ColorJsonConverterTests
{
private readonly ColorJsonConverter _converter = new ColorJsonConverter();
[Fact]
public void CanConvert_ColorType_ReturnsTrue()
{
var colorType = typeof(Color);
var result = _converter.CanConvert(colorType);
Assert.True(result);
}
[Fact]
public void CanConvert_NonColorType_ReturnsFalse()
{
var otherType = typeof(string);
var result = _converter.CanConvert(otherType);
Assert.False(result);
}
[Theory]
[InlineData("Red", 255, 0, 0, 255)]
[InlineData("#FF0000FF", 255, 0, 0, 255)]
public void Read_ValidColorJson_ReturnsExpectedColor(string jsonValue, byte r, byte g, byte b, byte a)
{
var json = $"\"{jsonValue}\"";
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
reader.Read();
var actual = _converter.Read(ref reader, typeof(Color), new JsonSerializerOptions());
var expected = new Color(r, g, b, a);
Assert.Equal(expected, actual);
}
[Fact]
public void Write_ValidColor_WritesExpectedJson()
{
var expected = "#ff000000";
var color = ColorHelper.FromHex(expected);
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
_converter.Write(writer, color, new JsonSerializerOptions());
writer.Flush();
var actual = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal($"\"{expected}\"", actual);
}
[Fact]
public void Write_NullWrier_ThrowArgumentNullException()
{
var color = Color.MonoGameOrange;
Assert.Throws<ArgumentNullException>(() => _converter.Write(null, color, new JsonSerializerOptions()));
}
}
@@ -1,6 +1,6 @@
using System.IO;
using System.Text.Json;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
using Xunit;
namespace MonoGame.Extended.Tests.Serialization;
@@ -18,12 +18,15 @@ public class RectangleFJsonConverterTest
{
var jsonData = @"
{
box: ""1 1 10 10""
""box"": ""1 1 10 10""
}
";
var serializer = new JsonSerializer();
serializer.Converters.Add(new RectangleFJsonConverter());
var content = serializer.Deserialize<TestContent>(new JsonTextReader(new StringReader(jsonData)));
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
options.Converters.Add(new RectangleFJsonConverter());
var content = JsonSerializer.Deserialize<TestContent>(jsonData, options);
Assert.Equal(1, content.Box.Left);
Assert.Equal(1, content.Box.Top);