mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
Start json reader
This commit is contained in:
parent
34dcca7f35
commit
0f05bd10aa
4 changed files with 352 additions and 0 deletions
32
DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs
Normal file
32
DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
namespace DotTiled.Tests;
|
||||||
|
|
||||||
|
public partial class TmjMapReaderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var jsonString =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"backgroundcolor":"#656667",
|
||||||
|
"height":4,
|
||||||
|
"nextobjectid":1,
|
||||||
|
"nextlayerid":1,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32,
|
||||||
|
"version":"1",
|
||||||
|
"tiledversion":"1.0.3",
|
||||||
|
"width":4
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using var tmjMapReader = new TmjMapReader(jsonString);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var map = tmjMapReader.ReadMap();
|
||||||
|
}
|
||||||
|
}
|
158
DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs
Normal file
158
DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
internal partial class Tmj
|
||||||
|
{
|
||||||
|
internal abstract class JsonProperty(string propertyName)
|
||||||
|
{
|
||||||
|
internal string PropertyName { get; } = propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class RequiredProperty<T>(string propertyName, Action<T> withValue) : JsonProperty(propertyName)
|
||||||
|
{
|
||||||
|
internal Action<T> WithValue { get; } = withValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class OptionalProperty<T>(string propertyName, Action<T?> withValue, bool allowNull = false) : JsonProperty(propertyName)
|
||||||
|
{
|
||||||
|
internal Action<T?> WithValue { get; } = withValue;
|
||||||
|
internal bool AllowNull { get; } = allowNull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ExtensionsUtf8JsonReader
|
||||||
|
{
|
||||||
|
private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
|
||||||
|
{
|
||||||
|
while (toCheck != typeof(object))
|
||||||
|
{
|
||||||
|
var cur = toCheck!.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
|
||||||
|
if (generic == cur)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
toCheck = toCheck.BaseType!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Require<T>(this ref Utf8JsonReader reader, ProcessProperty process)
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
|
throw new JsonException("Value is required.");
|
||||||
|
|
||||||
|
process(ref reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void MoveToContent(this ref Utf8JsonReader reader)
|
||||||
|
{
|
||||||
|
while (reader.Read() && reader.TokenType == JsonTokenType.Comment ||
|
||||||
|
reader.TokenType == JsonTokenType.None)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal delegate void ProcessProperty(ref Utf8JsonReader reader);
|
||||||
|
|
||||||
|
internal static void ProcessJsonObject(this Utf8JsonReader reader, (string PropertyName, ProcessProperty Processor)[] processors)
|
||||||
|
{
|
||||||
|
if (reader.TokenType != JsonTokenType.StartObject)
|
||||||
|
throw new JsonException("Expected start of object.");
|
||||||
|
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.EndObject)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||||
|
throw new JsonException("Expected property name.");
|
||||||
|
|
||||||
|
var propertyName = reader.GetString();
|
||||||
|
reader.Read();
|
||||||
|
|
||||||
|
if (!processors.Any(x => x.PropertyName == propertyName))
|
||||||
|
{
|
||||||
|
reader.Skip();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var processor = processors.First(x => x.PropertyName == propertyName).Processor;
|
||||||
|
processor(ref reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new JsonException("Expected end of object.");
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate T UseReader<T>(ref Utf8JsonReader reader);
|
||||||
|
|
||||||
|
internal static void ProcessJsonObject(this Utf8JsonReader reader, Tmj.JsonProperty[] properties)
|
||||||
|
{
|
||||||
|
List<string> processedProperties = [];
|
||||||
|
|
||||||
|
bool CheckType<T>(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader<T?> useReader)
|
||||||
|
{
|
||||||
|
return CheckRequire<T>(ref reader, prop, (ref Utf8JsonReader r) => useReader(ref r)!) || CheckOptional<T>(ref reader, prop, useReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckRequire<T>(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader<T> useReader)
|
||||||
|
{
|
||||||
|
if (prop is Tmj.RequiredProperty<T> requiredProp)
|
||||||
|
{
|
||||||
|
reader.Require<string>((ref Utf8JsonReader r) =>
|
||||||
|
{
|
||||||
|
requiredProp.WithValue(useReader(ref r));
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckOptional<T>(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader<T?> useReader)
|
||||||
|
{
|
||||||
|
if (prop is Tmj.OptionalProperty<T> optionalProp)
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.Null && !optionalProp.AllowNull)
|
||||||
|
throw new JsonException("Value cannot be null for optional property.");
|
||||||
|
else if (reader.TokenType == JsonTokenType.Null && optionalProp.AllowNull)
|
||||||
|
optionalProp.WithValue(default);
|
||||||
|
else
|
||||||
|
optionalProp.WithValue(useReader(ref reader));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessJsonObject(reader, properties.Select<Tmj.JsonProperty, (string, ProcessProperty)>(x => (x.PropertyName.ToLowerInvariant(), (ref Utf8JsonReader reader) =>
|
||||||
|
{
|
||||||
|
var lowerInvariant = x.PropertyName.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (processedProperties.Contains(lowerInvariant))
|
||||||
|
throw new JsonException($"Property '{lowerInvariant}' was already processed.");
|
||||||
|
|
||||||
|
processedProperties.Add(lowerInvariant);
|
||||||
|
|
||||||
|
if (CheckType<string>(ref reader, x, (ref Utf8JsonReader r) => r.GetString()!))
|
||||||
|
return;
|
||||||
|
if (CheckType<int>(ref reader, x, (ref Utf8JsonReader r) => r.GetInt32()))
|
||||||
|
return;
|
||||||
|
if (CheckType<uint>(ref reader, x, (ref Utf8JsonReader r) => r.GetUInt32()))
|
||||||
|
return;
|
||||||
|
if (CheckType<float>(ref reader, x, (ref Utf8JsonReader r) => r.GetSingle()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
throw new NotSupportedException($"Unsupported property type '{x.GetType().GenericTypeArguments.First()}'.");
|
||||||
|
}
|
||||||
|
)).ToArray());
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (IsSubclassOfRawGeneric(typeof(Tmj.RequiredProperty<>), property.GetType()) && !processedProperties.Contains(property.PropertyName.ToLowerInvariant()))
|
||||||
|
throw new JsonException($"Required property '{property.PropertyName}' was not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
DotTiled/Serialization/Tmj/Tmj.Map.cs
Normal file
102
DotTiled/Serialization/Tmj/Tmj.Map.cs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
internal partial class Tmj
|
||||||
|
{
|
||||||
|
internal static Map ReadMap(ref Utf8JsonReader reader)
|
||||||
|
{
|
||||||
|
string version = default!;
|
||||||
|
string tiledVersion = default!;
|
||||||
|
string @class = "";
|
||||||
|
MapOrientation orientation = default;
|
||||||
|
RenderOrder renderOrder = RenderOrder.RightDown;
|
||||||
|
int compressionLevel = -1;
|
||||||
|
uint width = 0;
|
||||||
|
uint height = 0;
|
||||||
|
uint tileWidth = 0;
|
||||||
|
uint tileHeight = 0;
|
||||||
|
uint? hexSideLength = null;
|
||||||
|
StaggerAxis? staggerAxis = null;
|
||||||
|
StaggerIndex? staggerIndex = null;
|
||||||
|
float parallaxOriginX = 0.0f;
|
||||||
|
float parallaxOriginY = 0.0f;
|
||||||
|
Color backgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
||||||
|
uint nextLayerID = 0;
|
||||||
|
uint nextObjectID = 0;
|
||||||
|
bool infinite = false;
|
||||||
|
|
||||||
|
reader.ProcessJsonObject([
|
||||||
|
new RequiredProperty<string>("version", value => version = value),
|
||||||
|
new RequiredProperty<string>("tiledVersion", value => tiledVersion = value),
|
||||||
|
new OptionalProperty<string>("class", value => @class = value ?? ""),
|
||||||
|
new RequiredProperty<string>("orientation", value => orientation = value switch
|
||||||
|
{
|
||||||
|
"orthogonal" => MapOrientation.Orthogonal,
|
||||||
|
"isometric" => MapOrientation.Isometric,
|
||||||
|
"staggered" => MapOrientation.Staggered,
|
||||||
|
"hexagonal" => MapOrientation.Hexagonal,
|
||||||
|
_ => throw new JsonException("Invalid orientation.")
|
||||||
|
}),
|
||||||
|
new OptionalProperty<string>("renderOrder", value => renderOrder = value switch
|
||||||
|
{
|
||||||
|
"right-down" => RenderOrder.RightDown,
|
||||||
|
"right-up" => RenderOrder.RightUp,
|
||||||
|
"left-down" => RenderOrder.LeftDown,
|
||||||
|
"left-up" => RenderOrder.LeftUp,
|
||||||
|
_ => throw new JsonException("Invalid render order.")
|
||||||
|
}),
|
||||||
|
new OptionalProperty<int>("compressionLevel", value => compressionLevel = value),
|
||||||
|
new RequiredProperty<uint>("width", value => width = value),
|
||||||
|
new RequiredProperty<uint>("height", value => height = value),
|
||||||
|
new RequiredProperty<uint>("tileWidth", value => tileWidth = value),
|
||||||
|
new RequiredProperty<uint>("tileHeight", value => tileHeight = value),
|
||||||
|
new OptionalProperty<uint>("hexSideLength", value => hexSideLength = value),
|
||||||
|
new OptionalProperty<string>("staggerAxis", value => staggerAxis = value switch
|
||||||
|
{
|
||||||
|
"x" => StaggerAxis.X,
|
||||||
|
"y" => StaggerAxis.Y,
|
||||||
|
_ => throw new JsonException("Invalid stagger axis.")
|
||||||
|
}),
|
||||||
|
new OptionalProperty<string>("staggerIndex", value => staggerIndex = value switch
|
||||||
|
{
|
||||||
|
"odd" => StaggerIndex.Odd,
|
||||||
|
"even" => StaggerIndex.Even,
|
||||||
|
_ => throw new JsonException("Invalid stagger index.")
|
||||||
|
}),
|
||||||
|
new OptionalProperty<float>("parallaxOriginX", value => parallaxOriginX = value),
|
||||||
|
new OptionalProperty<float>("parallaxOriginY", value => parallaxOriginY = value),
|
||||||
|
new OptionalProperty<string>("backgroundColor", value => backgroundColor = Color.Parse(value!, CultureInfo.InvariantCulture)),
|
||||||
|
new RequiredProperty<uint>("nextLayerID", value => nextLayerID = value),
|
||||||
|
new RequiredProperty<uint>("nextObjectID", value => nextObjectID = value),
|
||||||
|
new OptionalProperty<uint>("infinite", value => infinite = value == 1)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new Map
|
||||||
|
{
|
||||||
|
Version = version,
|
||||||
|
TiledVersion = tiledVersion,
|
||||||
|
Class = @class,
|
||||||
|
Orientation = orientation,
|
||||||
|
RenderOrder = renderOrder,
|
||||||
|
CompressionLevel = compressionLevel,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
TileWidth = tileWidth,
|
||||||
|
TileHeight = tileHeight,
|
||||||
|
HexSideLength = hexSideLength,
|
||||||
|
StaggerAxis = staggerAxis,
|
||||||
|
StaggerIndex = staggerIndex,
|
||||||
|
ParallaxOriginX = parallaxOriginX,
|
||||||
|
ParallaxOriginY = parallaxOriginY,
|
||||||
|
BackgroundColor = backgroundColor,
|
||||||
|
NextLayerID = nextLayerID,
|
||||||
|
NextObjectID = nextObjectID,
|
||||||
|
Infinite = infinite,
|
||||||
|
//Properties = properties,
|
||||||
|
//Tilesets = tilesets,
|
||||||
|
//Layers = layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
60
DotTiled/Serialization/Tmj/TmjMapReader.cs
Normal file
60
DotTiled/Serialization/Tmj/TmjMapReader.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DotTiled;
|
||||||
|
|
||||||
|
public class TmjMapReader : IMapReader
|
||||||
|
{
|
||||||
|
private string _jsonString;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
public TmjMapReader(string jsonString)
|
||||||
|
{
|
||||||
|
_jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map ReadMap()
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(_jsonString);
|
||||||
|
var options = new JsonReaderOptions
|
||||||
|
{
|
||||||
|
AllowTrailingCommas = true,
|
||||||
|
CommentHandling = JsonCommentHandling.Skip,
|
||||||
|
};
|
||||||
|
var reader = new Utf8JsonReader(bytes, options);
|
||||||
|
reader.MoveToContent();
|
||||||
|
|
||||||
|
return Tmj.ReadMap(ref reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
// TODO: set large fields to null
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
|
// ~TmjMapReader()
|
||||||
|
// {
|
||||||
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
// Dispose(disposing: false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
System.GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue