diff --git a/.gitattributes b/.gitattributes index d0c79dab..e9afe0a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs b/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs index d7575fd1..855a0d33 100644 --- a/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs +++ b/source/MonoGame.Extended.Content.Pipeline/Animations/AstridAnimatorImporter.cs @@ -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 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(jsonReader); - return new ContentImporterResult(filename, data); - } - } + var json = File.ReadAllText(filename); + var data = JsonSerializer.Deserialize(json); + return new ContentImporterResult(filename, data); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj b/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj index e80590a7..70b81e87 100644 --- a/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj +++ b/source/MonoGame.Extended.Content.Pipeline/MonoGame.Extended.Content.Pipeline.csproj @@ -15,9 +15,6 @@ - - @@ -25,10 +22,4 @@ - - - Always - - - diff --git a/source/MonoGame.Extended.Content.Pipeline/Newtonsoft.Json.dll b/source/MonoGame.Extended.Content.Pipeline/Newtonsoft.Json.dll deleted file mode 100755 index 1ffeabe6..00000000 Binary files a/source/MonoGame.Extended.Content.Pipeline/Newtonsoft.Json.dll and /dev/null differ diff --git a/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs b/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs index 97f33e9a..a80e654d 100644 --- a/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs +++ b/source/MonoGame.Extended.Content.Pipeline/TextureAtlases/TexturePackerJsonImporter.cs @@ -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(jsonReader); - } - } + var json = File.ReadAllText(filename); + return JsonSerializer.Deserialize(json); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Controls/Control.cs b/source/MonoGame.Extended.Gui/Controls/Control.cs index 714b6d15..5440fdff 100644 --- a/source/MonoGame.Extended.Gui/Controls/Control.cs +++ b/source/MonoGame.Extended.Gui/Controls/Control.cs @@ -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; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Element.cs b/source/MonoGame.Extended.Gui/Element.cs index 497983f4..0c364a9a 100644 --- a/source/MonoGame.Extended.Gui/Element.cs +++ b/source/MonoGame.Extended.Gui/Element.cs @@ -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 { diff --git a/source/MonoGame.Extended.Gui/Screen.cs b/source/MonoGame.Extended.Gui/Screen.cs index bb5618ff..821a1e2b 100644 --- a/source/MonoGame.Extended.Gui/Screen.cs +++ b/source/MonoGame.Extended.Gui/Screen.cs @@ -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(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(stream, options); } public static Screen FromFile(ContentManager contentManager, string path, params Type[] customControlTypes) @@ -147,4 +137,4 @@ namespace MonoGame.Extended.Gui } } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Serialization/AlignmentConverter.cs b/source/MonoGame.Extended.Gui/Serialization/AlignmentConverter.cs deleted file mode 100644 index defea24a..00000000 --- a/source/MonoGame.Extended.Gui/Serialization/AlignmentConverter.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/source/MonoGame.Extended.Gui/Serialization/ControlJsonConverter.cs b/source/MonoGame.Extended.Gui/Serialization/ControlJsonConverter.cs index 0f4dcf2a..a9c71f82 100644 --- a/source/MonoGame.Extended.Gui/Serialization/ControlJsonConverter.cs +++ b/source/MonoGame.Extended.Gui/Serialization/ControlJsonConverter.cs @@ -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 { 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) - { - } + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Control); - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + /// + 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); - } + /// + 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; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Serialization/ControlStyleJsonConverter.cs b/source/MonoGame.Extended.Gui/Serialization/ControlStyleJsonConverter.cs index d1368977..c31549b0 100644 --- a/source/MonoGame.Extended.Gui/Serialization/ControlStyleJsonConverter.cs +++ b/source/MonoGame.Extended.Gui/Serialization/ControlStyleJsonConverter.cs @@ -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 { private readonly Dictionary _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) + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(ControlStyle); + + /// + public override ControlStyle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var style = (ControlStyle)value; - var dictionary = new Dictionary { [_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>(reader); + var dictionary = JsonSerializer.Deserialize>(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) + /// + public override void Write(Utf8JsonWriter writer, ControlStyle value, JsonSerializerOptions options) { - return objectType == typeof(ControlStyle); + var style = (ControlStyle)value; + var dictionary = new Dictionary { [_typeProperty] = style.TargetType.Name }; + + foreach (var keyValuePair in style) + dictionary.Add(keyValuePair.Key, keyValuePair.Value); + + JsonSerializer.Serialize(writer, dictionary); } + + + + + + } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs b/source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs deleted file mode 100644 index ece192fa..00000000 --- a/source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs +++ /dev/null @@ -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(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; - } - } -} \ No newline at end of file diff --git a/source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializerOptionsProvider.cs b/source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializerOptionsProvider.cs new file mode 100644 index 00000000..5ebc880f --- /dev/null +++ b/source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializerOptionsProvider.cs @@ -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(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; + } +} diff --git a/source/MonoGame.Extended.Gui/Serialization/GuiNinePatchRegion2DJsonConverter.cs b/source/MonoGame.Extended.Gui/Serialization/GuiNinePatchRegion2DJsonConverter.cs index ea437541..9be58e55 100644 --- a/source/MonoGame.Extended.Gui/Serialization/GuiNinePatchRegion2DJsonConverter.cs +++ b/source/MonoGame.Extended.Gui/Serialization/GuiNinePatchRegion2DJsonConverter.cs @@ -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; - } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Serialization/GuiTextureAtlasJsonConverter.cs b/source/MonoGame.Extended.Gui/Serialization/GuiTextureAtlasJsonConverter.cs index ed3fe4d2..a0617cf8 100644 --- a/source/MonoGame.Extended.Gui/Serialization/GuiTextureAtlasJsonConverter.cs +++ b/source/MonoGame.Extended.Gui/Serialization/GuiTextureAtlasJsonConverter.cs @@ -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) + /// + 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; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Gui/Serialization/HorizontalAlignmentConverter.cs b/source/MonoGame.Extended.Gui/Serialization/HorizontalAlignmentConverter.cs new file mode 100644 index 00000000..a6965287 --- /dev/null +++ b/source/MonoGame.Extended.Gui/Serialization/HorizontalAlignmentConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MonoGame.Extended.Gui.Serialization; + +public class HorizontalAlignmentConverter : JsonConverter +{ + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(HorizontalAlignment); + + /// + 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(value, true, out var alignment)) + { + return alignment; + } + + throw new InvalidOperationException($"Invalid value for '{nameof(HorizontalAlignment)}'"); + } + + /// + public override void Write(Utf8JsonWriter writer, HorizontalAlignment value, JsonSerializerOptions options) { } +} diff --git a/source/MonoGame.Extended.Gui/Serialization/SkinJsonConverter.cs b/source/MonoGame.Extended.Gui/Serialization/SkinJsonConverter.cs index c167e9fd..016ea6d7 100644 --- a/source/MonoGame.Extended.Gui/Serialization/SkinJsonConverter.cs +++ b/source/MonoGame.Extended.Gui/Serialization/SkinJsonConverter.cs @@ -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 +{ + 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 + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Skin); + + /// + 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"); } -} \ No newline at end of file + + /// + public override void Write(Utf8JsonWriter writer, Skin value, JsonSerializerOptions options) { } +} diff --git a/source/MonoGame.Extended.Gui/Serialization/VerticalAlignmentConverter.cs b/source/MonoGame.Extended.Gui/Serialization/VerticalAlignmentConverter.cs new file mode 100644 index 00000000..bc55cda2 --- /dev/null +++ b/source/MonoGame.Extended.Gui/Serialization/VerticalAlignmentConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MonoGame.Extended.Gui.Serialization; + +public class VerticalAlignmentConverter : JsonConverter +{ + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(VerticalAlignment); + + /// + 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(value, true, out var alignment)) + { + return alignment; + } + + throw new InvalidOperationException($"Invalid value for '{nameof(VerticalAlignment)}'"); + } + + /// + public override void Write(Utf8JsonWriter writer, VerticalAlignment value, JsonSerializerOptions options) { } +} diff --git a/source/MonoGame.Extended.Gui/Skin.cs b/source/MonoGame.Extended.Gui/Skin.cs index 0ff9dcfb..77476be0 100644 --- a/source/MonoGame.Extended.Gui/Skin.cs +++ b/source/MonoGame.Extended.Gui/Skin.cs @@ -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(s => s.Name ?? s.TargetType.Name); } - [JsonProperty(Order = 0)] + [JsonPropertyOrder(0)] public string Name { get; set; } - [JsonProperty(Order = 1)] + [JsonPropertyOrder(1)] public IList TextureAtlases { get; set; } - [JsonProperty(Order = 2)] + [JsonPropertyOrder(2)] public IList Fonts { get; set; } - [JsonProperty(Order = 3)] + [JsonPropertyOrder(3)] public IList 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 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(jsonReader); - } + var options = GuiJsonSerializerOptionsProvider.GetOptions(contentManager, customControlTypes); + return JsonSerializer.Deserialize(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; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended.Particles/ParticleEffect.cs b/source/MonoGame.Extended.Particles/ParticleEffect.cs index 628980e0..ce6c7820 100644 --- a/source/MonoGame.Extended.Particles/ParticleEffect.cs +++ b/source/MonoGame.Extended.Particles/ParticleEffect.cs @@ -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(jsonReader); - } + var options = ParticleJsonSerializerOptionsProvider.GetOptions(textureRegionService); + return JsonSerializer.Deserialize(stream, options); } public void Update(float elapsedSeconds) diff --git a/source/MonoGame.Extended.Particles/ParticleEmitter.cs b/source/MonoGame.Extended.Particles/ParticleEmitter.cs index 05ac2839..1a6273b2 100644 --- a/source/MonoGame.Extended.Particles/ParticleEmitter.cs +++ b/source/MonoGame.Extended.Particles/ParticleEmitter.cs @@ -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; diff --git a/source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs b/source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs index 005f8416..8fc894bb 100644 --- a/source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs +++ b/source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class ModifierExecutionStrategyJsonConverter : JsonConverter { - public class ModifierExecutionStrategyJsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => + typeToConvert == typeof(ParticleModifierExecutionStrategy) || + typeToConvert.GetTypeInfo().BaseType == typeof(ParticleModifierExecutionStrategy); + + /// + 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(); - return ParticleModifierExecutionStrategy.Parse(value); - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(ParticleModifierExecutionStrategy) - || objectType.GetTypeInfo().BaseType == typeof(ParticleModifierExecutionStrategy); - } + var value = JsonSerializer.Deserialize(ref reader, options); + return ParticleModifierExecutionStrategy.Parse(value); } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, ParticleModifierExecutionStrategy value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteStringValue(value.ToString()); + } +} diff --git a/source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs b/source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs deleted file mode 100644 index 39e7d8c6..00000000 --- a/source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs +++ /dev/null @@ -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()); - Converters.Add(new RangeJsonConverter()); - Converters.Add(new RangeJsonConverter()); - Converters.Add(new HslColorJsonConverter()); - Converters.Add(new ModifierExecutionStrategyJsonConverter()); - ContractResolver = new ShortNameJsonContractResolver(); - NullValueHandling = nullValueHandling; - Formatting = Formatting.Indented; - } - } -} diff --git a/source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializerOptionsProvider.cs b/source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializerOptionsProvider.cs new file mode 100644 index 00000000..489b3eee --- /dev/null +++ b/source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializerOptionsProvider.cs @@ -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()); + options.Converters.Add(new RangeJsonConverter()); + options.Converters.Add(new RangeJsonConverter()); + options.Converters.Add(new HslColorJsonConverter()); + options.Converters.Add(new ModifierExecutionStrategyJsonConverter()); + + return options; + } +} diff --git a/source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs b/source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs index a2159129..0155b083 100644 --- a/source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs +++ b/source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs @@ -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 { - public class TimeSpanJsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TimeSpan); + + /// + 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; } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteNumberValue(value.TotalSeconds); + } +} diff --git a/source/MonoGame.Extended/MonoGame.Extended.csproj b/source/MonoGame.Extended/MonoGame.Extended.csproj index 68b60847..e99bbe59 100644 --- a/source/MonoGame.Extended/MonoGame.Extended.csproj +++ b/source/MonoGame.Extended/MonoGame.Extended.csproj @@ -9,7 +9,6 @@ - diff --git a/source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs b/source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs index 29858fd7..cdbec1e1 100644 --- a/source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs @@ -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 : JsonConverter + public abstract class BaseTypeJsonConverter : JsonConverter { private readonly string _suffix; private readonly Dictionary _namesToTypes; private readonly Dictionary _typesToNames; - private readonly CamelCaseNamingStrategy _namingStrategy = new CamelCaseNamingStrategy(); + private readonly JsonSerializerOptions _serializerOptions; + private readonly JsonNamingPolicy _namingPolicy = JsonNamingPolicy.CamelCase; protected BaseTypeJsonConverter(IEnumerable 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) + /// + public override bool CanConvert(Type typeToConvert) => + _namesToTypes.ContainsValue(typeToConvert) || typeof(T) == typeToConvert; + + /// + /// + 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}'"); + } + } + + /// + /// + /// Throw if is . + /// + 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(); - 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; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/Serialization/ColorJsonConverter.cs b/source/MonoGame.Extended/Serialization/ColorJsonConverter.cs index ce2caabd..41ceabed 100644 --- a/source/MonoGame.Extended/Serialization/ColorJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/ColorJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class ColorJsonConverter : JsonConverter { - public class ColorJsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Color); + + /// + 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); } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + var hexValue = ColorHelper.ToHex(value); + writer.WriteStringValue(hexValue); + } +} + diff --git a/source/MonoGame.Extended/Serialization/ContentManagerJsonConverter.cs b/source/MonoGame.Extended/Serialization/ContentManagerJsonConverter.cs index 40b82151..6b57bfeb 100644 --- a/source/MonoGame.Extended/Serialization/ContentManagerJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/ContentManagerJsonConverter.cs @@ -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; + +/// +/// Loads content from a JSON file into the using the asset name +/// +/// The type of content to load +public class ContentManagerJsonConverter : JsonConverter { + private readonly ContentManager _contentManager; + private readonly Func _getAssetName; + /// - /// Loads content from a JSON file into the using the asset name + /// Initializes a new instance of the class. /// - /// The type of content to load - public class ContentManagerJsonConverter : JsonConverter + /// The used to load content. + /// A function that returns the asset name for a given instance of . + public ContentManagerJsonConverter(ContentManager contentManager, Func getAssetName) { - private readonly ContentManager _contentManager; - private readonly Func _getAssetName; - - public ContentManagerJsonConverter(ContentManager contentManager, Func 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(assetName); - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(T); - } + _contentManager = contentManager; + _getAssetName = getAssetName; } -} \ No newline at end of file + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(T); + + /// + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var assetName = reader.GetString(); + return _contentManager.Load(assetName); + } + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + var asset = (T)value; + var assetName = _getAssetName(asset); + writer.WriteStringValue(assetName); + } +} diff --git a/source/MonoGame.Extended/Serialization/FloatStringConverter.cs b/source/MonoGame.Extended/Serialization/FloatStringConverter.cs new file mode 100644 index 00000000..a19c5895 --- /dev/null +++ b/source/MonoGame.Extended/Serialization/FloatStringConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MonoGame.Extended.Serialization; + +public class FloatStringConverter : JsonConverter +{ + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(float) || typeToConvert == typeof(string); + + /// + 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)}"); + } + + /// + public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteNumberValue(value); + } +} diff --git a/source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs b/source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs index dca71fe5..6d63a158 100644 --- a/source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class HslColorJsonConverter : JsonConverter { - public class HslColorJsonConverter : JsonConverter + private readonly ColorJsonConverter _colorConverter = new ColorJsonConverter(); + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(HslColor); + + /// + 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); } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, HslColor value, JsonSerializerOptions options) + { + var color = ((HslColor)value).ToRgb(); + _colorConverter.Write(writer, color, options); + } +} diff --git a/source/MonoGame.Extended/Serialization/JsonContentLoader.cs b/source/MonoGame.Extended/Serialization/JsonContentLoader.cs index 127d5fd4..69a1212d 100644 --- a/source/MonoGame.Extended/Serialization/JsonContentLoader.cs +++ b/source/MonoGame.Extended/Serialization/JsonContentLoader.cs @@ -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(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(jsonReader); - } + + using var stream = contentManager.OpenStream(path); + var monoGameSerializerOptions = MonoGameJsonSerializerOptionsProvider.GetOptions(contentManager, path); + return JsonSerializer.Deserialize(stream, monoGameSerializerOptions); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/Serialization/JsonContentTypeReader.cs b/source/MonoGame.Extended/Serialization/JsonContentTypeReader.cs index f440875a..1d5609ef 100644 --- a/source/MonoGame.Extended/Serialization/JsonContentTypeReader.cs +++ b/source/MonoGame.Extended/Serialization/JsonContentTypeReader.cs @@ -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(jsonReader); - } + return JsonSerializer.Deserialize(json); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs b/source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs deleted file mode 100644 index 5795888f..00000000 --- a/source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs +++ /dev/null @@ -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> _stringParsers = new Dictionary> - { - {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(this JsonReader reader) - { - var tokenType = reader.TokenType; - - switch (tokenType) - { - case JsonToken.StartArray: - return reader.ReadAsJArray(); - - case JsonToken.String: - return reader.ReadAsDelimitedString(); - - case JsonToken.Integer: - case JsonToken.Float: - return reader.ReadAsSingleValue(); - - default: - throw new NotSupportedException($"{tokenType} is not currently supported in the multi dimensional parser"); - } - } - - private static T[] ReadAsSingleValue(this JsonReader reader) - { - return new[] { JToken.Load(reader).ToObject() }; - } - - private static T[] ReadAsJArray(this JsonReader reader) - { - var jArray = JArray.Load(reader); - var items = new List(); - - foreach (var token in jArray) - { - if (token.Type == JTokenType.String) - { - var stringParser = _stringParsers[typeof(T)]; - var s = token.Value(); - items.Add((T)stringParser(s)); - } - else - { - items.Add(token.Value()); - } - } - - return items.ToArray(); - } - - private static T[] ReadAsDelimitedString(this JsonReader reader) - { - var value = (string)reader.Value; - var parser = _stringParsers[typeof(T)]; - return value.Split(' ') - .Select(i => parser(i)) - .Cast() - .ToArray(); - } - } -} \ No newline at end of file diff --git a/source/MonoGame.Extended/Serialization/MonoGameJsonSerializer.cs b/source/MonoGame.Extended/Serialization/MonoGameJsonSerializer.cs deleted file mode 100644 index eb037f62..00000000 --- a/source/MonoGame.Extended/Serialization/MonoGameJsonSerializer.cs +++ /dev/null @@ -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()); - Converters.Add(new RangeJsonConverter()); - Converters.Add(new RangeJsonConverter()); - 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; - } - } -} diff --git a/source/MonoGame.Extended/Serialization/MonoGameJsonSerializerOptionsProvider.cs b/source/MonoGame.Extended/Serialization/MonoGameJsonSerializerOptionsProvider.cs new file mode 100644 index 00000000..32a099fc --- /dev/null +++ b/source/MonoGame.Extended/Serialization/MonoGameJsonSerializerOptionsProvider.cs @@ -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()); + options.Converters.Add(new RangeJsonConverter()); + options.Converters.Add(new RangeJsonConverter()); + options.Converters.Add(new ThicknessJsonConverter()); + options.Converters.Add(new RectangleFJsonConverter()); + options.Converters.Add(new TextureAtlasJsonConverter(contentManager, contentPath)); + options.Converters.Add(new Size2JsonConverter()); + + return options; + } +} diff --git a/source/MonoGame.Extended/Serialization/NinePatchRegion2DJsonConverter.cs b/source/MonoGame.Extended/Serialization/NinePatchRegion2DJsonConverter.cs index ca4789fe..44bbbf79 100644 --- a/source/MonoGame.Extended/Serialization/NinePatchRegion2DJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/NinePatchRegion2DJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class NinePatchRegion2DJsonConverter : JsonConverter { - public class NinePatchRegion2DJsonConverter : JsonConverter + private readonly ITextureRegionService _textureRegionService; + + /// + /// Initializes a new instance of the class. + /// + /// The texture region service used to retrieve texture regions. + 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(reader); - var paddingAsString = jsonObject.Value("Padding"); - var thickness = Thickness.Parse(paddingAsString); - var regionName = jsonObject.Value("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; } -} \ No newline at end of file + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(NinePatchRegion2D); + + /// + /// + /// Thrown if the JSON property does not contain a properly formatted value + /// + 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); + } + + /// + /// + /// Throw if is . + /// + 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(); + } +} diff --git a/source/MonoGame.Extended/Serialization/RangeJsonConverter.cs b/source/MonoGame.Extended/Serialization/RangeJsonConverter.cs index f33ddb66..ef5b5cb1 100644 --- a/source/MonoGame.Extended/Serialization/RangeJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/RangeJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class RangeJsonConverter : JsonConverter> where T : IComparable { - public class RangeJsonConverter : JsonConverter where T : IComparable + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Range); + + /// + public override Range Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + Span values = reader.ReadAsMultiDimensional(); + + if (values.Length == 2) { - var range = (Range) 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(); - - if (values.Length == 2) + if (values[0].CompareTo(values[1]) < 0) { - if (values[0].CompareTo(values[1]) < 0) - return new Range(values[0], values[1]); - - return new Range(values[1], values[0]); + return new Range(values[0], values[1]); } - - if (values.Length == 1) - return new Range(values[0], values[0]); - - throw new InvalidOperationException("Invalid range"); + return new Range(values[1], values[0]); } - public override bool CanConvert(Type objectType) + if (values.Length == 1) { - return objectType == typeof(Range); + return new Range(values[0], values[0]); } + + throw new InvalidOperationException("Invalid range"); } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, Range value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteStartArray(); + JsonSerializer.Serialize(writer, value.Min, options); + JsonSerializer.Serialize(writer, value.Max, options); + writer.WriteEndArray(); + } +} diff --git a/source/MonoGame.Extended/Serialization/RectangleFJsonConverter.cs b/source/MonoGame.Extended/Serialization/RectangleFJsonConverter.cs index ca8d2f57..080400a6 100644 --- a/source/MonoGame.Extended/Serialization/RectangleFJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/RectangleFJsonConverter.cs @@ -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 +/// +/// Converts a value to or from JSON. +/// +public class RectangleFJsonConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var rect = (RectangleF)value; - writer.WriteValue($"{rect.Left} {rect.Top} {rect.Width} {rect.Height}"); - } + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(RectangleF); - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + /// + public override RectangleF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var values = reader.ReadAsMultiDimensional(); return new RectangleF(values[0], values[1], values[2], values[3]); } - public override bool CanConvert(Type objectType) + /// + /// + /// Throw if is . + /// + 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}"); } } diff --git a/source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs b/source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs deleted file mode 100644 index e31adfa1..00000000 --- a/source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs +++ /dev/null @@ -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 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; - } - } - } -} \ No newline at end of file diff --git a/source/MonoGame.Extended/Serialization/Size2JsonConverter.cs b/source/MonoGame.Extended/Serialization/Size2JsonConverter.cs index f42c8fbd..9eeb329f 100644 --- a/source/MonoGame.Extended/Serialization/Size2JsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Size2JsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class Size2JsonConverter : JsonConverter { - public class Size2JsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Size2); + + /// + /// + /// Thrown if the JSON property does not contain a properly formatted value + /// + 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(); + + 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(); - - 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"); } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, Size2 value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteStringValue($"{value.Width} {value.Height}"); + } +} diff --git a/source/MonoGame.Extended/Serialization/SizeJsonConverter.cs b/source/MonoGame.Extended/Serialization/SizeJsonConverter.cs index 0e2455f0..3b454a19 100644 --- a/source/MonoGame.Extended/Serialization/SizeJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/SizeJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class SizeJsonConverter : JsonConverter { - public class SizeJsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Size); + + /// + /// + /// Thrown if the JSON property does not contain a properly formatted value + /// + 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(); + + 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(); - - 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"); + } + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, Size value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteStringValue($"{value.Width} {value.Height}"); } } diff --git a/source/MonoGame.Extended/Serialization/TextureRegion2DJsonConverter.cs b/source/MonoGame.Extended/Serialization/TextureRegion2DJsonConverter.cs index 648b9cf7..dbcb66cf 100644 --- a/source/MonoGame.Extended/Serialization/TextureRegion2DJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/TextureRegion2DJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class TextureRegion2DJsonConverter : JsonConverter { - public class TextureRegion2DJsonConverter : JsonConverter + private readonly ITextureRegionService _textureRegionService; + + /// + /// Initializes a new instance of the class. + /// + /// The texture region service to use for retrieving texture regions. + /// + /// Thrown if is . + /// + 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; } -} \ No newline at end of file + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TextureRegion2D); + + /// + public override TextureRegion2D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var regionName = reader.GetString(); + return string.IsNullOrEmpty(regionName) ? null : _textureRegionService.GetTextureRegion(regionName); + } + + /// + /// + /// Throw if is . + /// + /// -or- + /// + /// Thrown if is . + /// + public override void Write(Utf8JsonWriter writer, TextureRegion2D value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + ArgumentNullException.ThrowIfNull(value); + writer.WriteStringValue(value.Name); + } +} diff --git a/source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs b/source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs index a4a57239..1a60573f 100644 --- a/source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class ThicknessJsonConverter : JsonConverter { - public class ThicknessJsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Thickness); + + /// + 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(); - return Thickness.FromValues(values); - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(Thickness); - } + var values = reader.ReadAsMultiDimensional(); + return Thickness.FromValues(values); } -} \ No newline at end of file + + /// + /// + /// Throw if is + /// + public override void Write(Utf8JsonWriter writer, Thickness value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteStringValue($"{value.Left} {value.Top} {value.Right} {value.Bottom}"); + } +} diff --git a/source/MonoGame.Extended/Serialization/Utf8JsonReaderExtensions.cs b/source/MonoGame.Extended/Serialization/Utf8JsonReaderExtensions.cs new file mode 100644 index 00000000..73ab532d --- /dev/null +++ b/source/MonoGame.Extended/Serialization/Utf8JsonReaderExtensions.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json; + +namespace MonoGame.Extended.Serialization; + +/// +/// Provides extension methods for working with . +/// +public static class Utf8JsonReaderExtensions +{ + private static readonly Dictionary> s_stringParsers = new Dictionary> + { + {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() } + }; + + /// + /// Reads a multi-dimensional JSON array and converts it to an array of the specified type. + /// + /// The type of the array elements. + /// The to read from. + /// An array of the specified type. + /// Thrown when the token type is not supported. + public static T[] ReadAsMultiDimensional(this ref Utf8JsonReader reader) + { + var tokenType = reader.TokenType; + + switch (tokenType) + { + case JsonTokenType.StartArray: + return reader.ReadAsJArray(); + + case JsonTokenType.String: + return reader.ReadAsDelimitedString(); + + case JsonTokenType.Number: + return reader.ReadAsSingleValue(); + + default: + throw new NotSupportedException($"{tokenType} is not currently supported in the multi-dimensional parser"); + } + } + + private static T[] ReadAsSingleValue(this ref Utf8JsonReader reader) + { + var token = JsonDocument.ParseValue(ref reader).RootElement; + var value = JsonSerializer.Deserialize(token.GetRawText()); + return new T[] { value }; + } + + private static T[] ReadAsJArray(this ref Utf8JsonReader reader) + { + var items = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + items.Add(JsonSerializer.Deserialize(ref reader)); + } + + return items.ToArray(); + } + + private static T[] ReadAsDelimitedString(this ref Utf8JsonReader reader) + { + var value = reader.GetString(); + if (string.IsNullOrEmpty(value)) + { + return Array.Empty(); + } + + Span 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; + } +} diff --git a/source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs b/source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs index 0f4f9cda..e10c72b2 100644 --- a/source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Vector2JsonConverter.cs @@ -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; + +/// +/// Converts a value to or from JSON. +/// +public class Vector2JsonConverter : JsonConverter { - public class Vector2JsonConverter : JsonConverter + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Vector2); + + /// + /// + /// Thrown if the JSON property does not contain a properly formatted value + /// + 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(); + + 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(); - - 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"); } -} \ No newline at end of file + + /// + /// + /// Throw if is . + /// + public override void Write(Utf8JsonWriter writer, Vector2 value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + writer.WriteStringValue($"{value.X} {value.Y}"); + } +} diff --git a/source/MonoGame.Extended/Sprites/SpriteSheetAnimationFrame.cs b/source/MonoGame.Extended/Sprites/SpriteSheetAnimationFrame.cs index cb38822d..2de8e39b 100644 --- a/source/MonoGame.Extended/Sprites/SpriteSheetAnimationFrame.cs +++ b/source/MonoGame.Extended/Sprites/SpriteSheetAnimationFrame.cs @@ -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 { - 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) + /// + public override SpriteSheetAnimationFrame Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) { - case JsonToken.Integer: - { - var index = serializer.Deserialize(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(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); + } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonContentTypeReader.cs b/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonContentTypeReader.cs index e339679b..20a7ae6a 100644 --- a/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonContentTypeReader.cs +++ b/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonContentTypeReader.cs @@ -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(jsonReader); - } + return JsonSerializer.Deserialize(json); } protected override TextureAtlas Read(ContentReader reader, TextureAtlas existingInstance) @@ -44,4 +38,4 @@ namespace MonoGame.Extended.TextureAtlases return atlas; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonConverter.cs b/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonConverter.cs index a0177582..2d887091 100644 --- a/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonConverter.cs +++ b/source/MonoGame.Extended/TextureAtlases/TextureAtlasJsonConverter.cs @@ -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 { 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; } - } + /// + 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(contentPath, new JsonContentLoader()); var texture = _contentManager.Load(texturePackerFile.Metadata.Image); @@ -44,7 +38,7 @@ namespace MonoGame.Extended.TextureAtlases } else { - var metadata = serializer.Deserialize(reader); + var metadata = JsonSerializer.Deserialize(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(resolvedAssetName); } - catch (Exception ex) { - if (textureDirectory == null || textureDirectory == "") + catch (Exception ex) + { + if (textureDirectory == null || textureDirectory == "") texture = _contentManager.Load(textureName); else texture = _contentManager.Load(textureDirectory + "/" + textureName); @@ -68,15 +63,22 @@ namespace MonoGame.Extended.TextureAtlases } } + /// + 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); - } } } diff --git a/source/MonoGame.Extended/TextureAtlases/TexturePackerFile.cs b/source/MonoGame.Extended/TextureAtlases/TexturePackerFile.cs index 69935842..b2cc243b 100644 --- a/source/MonoGame.Extended/TextureAtlases/TexturePackerFile.cs +++ b/source/MonoGame.Extended/TextureAtlases/TexturePackerFile.cs @@ -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 Regions { get; set; } - [JsonProperty("meta")] + [JsonPropertyName("meta")] public TexturePackerMeta Metadata { get; set; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/TextureAtlases/TexturePackerMeta.cs b/source/MonoGame.Extended/TextureAtlases/TexturePackerMeta.cs index 9ac6939b..5cdf21d0 100644 --- a/source/MonoGame.Extended/TextureAtlases/TexturePackerMeta.cs +++ b/source/MonoGame.Extended/TextureAtlases/TexturePackerMeta.cs @@ -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; } } -} \ No newline at end of file +} + diff --git a/source/MonoGame.Extended/TextureAtlases/TexturePackerPoint.cs b/source/MonoGame.Extended/TextureAtlases/TexturePackerPoint.cs index c4444f5c..c4b5acdb 100644 --- a/source/MonoGame.Extended/TextureAtlases/TexturePackerPoint.cs +++ b/source/MonoGame.Extended/TextureAtlases/TexturePackerPoint.cs @@ -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); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/TextureAtlases/TexturePackerRectangle.cs b/source/MonoGame.Extended/TextureAtlases/TexturePackerRectangle.cs index 3af4ee05..21f3d3be 100644 --- a/source/MonoGame.Extended/TextureAtlases/TexturePackerRectangle.cs +++ b/source/MonoGame.Extended/TextureAtlases/TexturePackerRectangle.cs @@ -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); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/TextureAtlases/TexturePackerRegion.cs b/source/MonoGame.Extended/TextureAtlases/TexturePackerRegion.cs index b38d4ebb..8664aa6d 100644 --- a/source/MonoGame.Extended/TextureAtlases/TexturePackerRegion.cs +++ b/source/MonoGame.Extended/TextureAtlases/TexturePackerRegion.cs @@ -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}"; } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/TextureAtlases/TexturePackerSize.cs b/source/MonoGame.Extended/TextureAtlases/TexturePackerSize.cs index 6a0c8f73..05351d0f 100644 --- a/source/MonoGame.Extended/TextureAtlases/TexturePackerSize.cs +++ b/source/MonoGame.Extended/TextureAtlases/TexturePackerSize.cs @@ -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}"; } } -} \ No newline at end of file +} diff --git a/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs b/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs new file mode 100644 index 00000000..ae8e1e71 --- /dev/null +++ b/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs @@ -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(() => _converter.Write(null, color, new JsonSerializerOptions())); + } +} diff --git a/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs b/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs index 8a386518..96f9c4f2 100644 --- a/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs +++ b/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs @@ -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(new JsonTextReader(new StringReader(jsonData))); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + options.Converters.Add(new RectangleFJsonConverter()); + var content = JsonSerializer.Deserialize(jsonData, options); Assert.Equal(1, content.Box.Left); Assert.Equal(1, content.Box.Top);