From 0ed39efd82651a97da826381bf879319eca5b621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20BECHER?= Date: Fri, 15 Mar 2024 23:47:34 +0100 Subject: [PATCH] Implement my own JSON serializer --- Nbt/Serialization/JsonNbt.cs | 525 ++++++++++++++++++++++------------- 1 file changed, 338 insertions(+), 187 deletions(-) diff --git a/Nbt/Serialization/JsonNbt.cs b/Nbt/Serialization/JsonNbt.cs index 7cff8c5..27f98b6 100755 --- a/Nbt/Serialization/JsonNbt.cs +++ b/Nbt/Serialization/JsonNbt.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Text; using System.Text.Json; using Nbt.Tag; @@ -9,93 +10,93 @@ public static class JsonNbt private static readonly JsonEncodedText TypeProperty = JsonEncodedText.Encode("type"); private static readonly JsonEncodedText ValueProperty = JsonEncodedText.Encode("value"); - #region Serialize lossless + #region Serialize - public static string Serialize(INbtTag tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtList tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtCompound tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtArray tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtArray tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtArray tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtArray tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); - public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag)); + #region Lossless - public static void Serialize(TextWriter writer, INbtTag tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtList tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtCompound tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtArray tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtArray tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtArray tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtArray tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); - public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag)); + public enum SerializationStyle + { + Compact, Spaced, Indented + } - public static void Serialize(Stream stream, INbtTag tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtList tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtCompound tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtArray tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtArray tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtArray tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtArray tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); - public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag)); + public class SerializerOptions + { + public static readonly SerializerOptions Default = new(); - private static string Serialize(Action serialize) + public SerializationStyle Style { get; init; } = SerializationStyle.Compact; + public string Indent { get; init; } = " "; + } + + public static string Serialize(INbtTag tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtList tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtCompound tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtArray tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtArray tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtArray tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtArray tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag)); + + public static void Serialize(TextWriter writer, INbtTag tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtList tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtCompound tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag)); + + public static void Serialize(Stream stream, INbtTag tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtList tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtCompound tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + public static void Serialize(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => Serialize(stream, encoding, options, serializer => serializer.Serialize(tag)); + + private static string Serialize(SerializerOptions? options, Action serialize) { using var writer = new StringWriter(); - Serialize(writer, serialize); + Serialize(writer, options, serialize); return writer.ToString(); } - private static void Serialize(TextWriter writer, Action serialize) + private static void Serialize(Stream stream, Encoding? encoding, SerializerOptions? options, Action serialize) { - using var stream = new MemoryStream(); - Serialize(stream, serialize); - stream.Position = 0L; - - using var reader = new StreamReader(stream, encoding: Encoding.UTF8, leaveOpen: true); - - while (true) - { - var i = reader.Read(); - - if (i < 0) - { - break; - } - - writer.Write((char)i); - } + using var writer = new StreamWriter(stream, encoding ?? Encoding.Default); + Serialize(writer, options, serialize); } - private static void Serialize(Stream stream, Action serialize) + private static void Serialize(TextWriter writer, SerializerOptions? options, Action serialize) { - using var serializer = new Serializer(stream); + using var serializer = new LosslessSerializer(writer, options); serialize(serializer); } - private sealed class Serializer(Stream stream) : IDisposable + private sealed class LosslessSerializer(TextWriter writer, SerializerOptions? options, bool leaveOpen = false) : IDisposable { - private readonly Utf8JsonWriter writer = new(stream, new() { Indented = true, SkipValidation = true }); + private readonly JsonWriter writer = new(writer, options, leaveOpen); public void Serialize(INbtTag tag) { @@ -121,13 +122,13 @@ public static class JsonNbt public void Serialize(INbtList tag) { - using (new NbtObjectMeta(writer, tag.Type)) + using (new NbtObjectMeta(writer, tag)) { writer.WriteStartArray(); foreach (var entry in tag) { - Serialize(entry); + writer.WriteElement(entry, Serialize); } writer.WriteEndArray(); @@ -136,14 +137,13 @@ public static class JsonNbt public void Serialize(INbtCompound tag) { - using (new NbtObjectMeta(writer, tag.Type)) + using (new NbtObjectMeta(writer, tag)) { - writer.WriteStartObject(); + writer. WriteStartObject(); foreach (var entry in tag as IEnumerable) { - writer.WritePropertyName(entry.Name); - Serialize(entry.Tag); + writer.WriteProperty(entry.Name, entry.Tag, Serialize); } writer.WriteEndObject(); @@ -216,13 +216,13 @@ public static class JsonNbt private void Serialize(INbtArray tag, Action writeValue) where T : notnull { - using (new NbtObjectMeta(writer, tag.Type)) + using (new NbtObjectMeta(writer, tag)) { writer.WriteStartArray(); - foreach (var e in tag) + foreach (var element in tag) { - writeValue(e); + writer.WriteElement(element, writeValue); } writer.WriteEndArray(); @@ -231,19 +231,19 @@ public static class JsonNbt private void Serialize(INbtValue tag, Action writeValue) where T : notnull { - using (new NbtObjectMeta(writer, tag.Type)) + using (new NbtObjectMeta(writer, tag)) { writeValue(tag.Value); } } - private void Serialize(sbyte value) => writer.WriteNumberValue(value); - private void Serialize(short value) => writer.WriteNumberValue(value); - private void Serialize(int value) => writer.WriteNumberValue(value); - private void Serialize(long value) => writer.WriteNumberValue(value); - private void Serialize(float value) => writer.WriteNumberValue(value); - private void Serialize(double value) => writer.WriteNumberValue(value); - private void Serialize(string value) => writer.WriteStringValue(value); + private void Serialize(sbyte value) => writer.WriteValue(value); + private void Serialize(short value) => writer.WriteValue(value); + private void Serialize(int value) => writer.WriteValue(value); + private void Serialize(long value) => writer.WriteValue(value); + private void Serialize(float value) => writer.WriteValue(value); + private void Serialize(double value) => writer.WriteValue(value); + private void Serialize(string value) => writer.WriteValue(value); public void Dispose() { @@ -251,116 +251,82 @@ public static class JsonNbt GC.SuppressFinalize(this); } - - private readonly struct NbtObjectMeta : IDisposable - { - private readonly Utf8JsonWriter writer; - - public NbtObjectMeta(Utf8JsonWriter writer, NbtTagType tagType) - { - this.writer = writer; - - writer.WriteStartObject(); - writer.WriteNumber(TypeProperty, (int)tagType); - writer.WritePropertyName(ValueProperty); - } - - public void Dispose() - { - writer.WriteEndObject(); - } - } } #endregion - #region Serialize lossy + #region Lossy - public static string ToJson(INbtTag tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtList tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtCompound tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtArray tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtArray tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtArray tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtArray tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); - public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag)); + public static string ToJson(INbtTag tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtList tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtCompound tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtArray tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtArray tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtArray tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtArray tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); + public static string ToJson(INbtValue tag, SerializerOptions? options = null) => ToJson(options, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtTag tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtList tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtCompound tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtArray tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtArray tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtArray tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtArray tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); - public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtTag tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtList tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtCompound tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); + public static void ToJson(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => ToJson(writer, options, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtTag tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtList tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtCompound tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtArray tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtArray tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtArray tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtArray tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); - public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtTag tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtList tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtCompound tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtArray tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); + public static void ToJson(Stream stream, INbtValue tag, Encoding? encoding = null, SerializerOptions? options = null) => ToJson(stream, encoding, options, serializer => serializer.ToJson(tag)); - private static string ToJson(Action serialize) + private static string ToJson(SerializerOptions? options, Action serialize) { using var writer = new StringWriter(); - ToJson(writer, serialize); + ToJson(writer, options, serialize); return writer.ToString(); } - private static void ToJson(TextWriter writer, Action serialize) + private static void ToJson(Stream stream, Encoding? encoding, SerializerOptions? options, Action serialize) { - using var stream = new MemoryStream(); - ToJson(stream, serialize); - stream.Position = 0L; - - using var reader = new StreamReader(stream, encoding: Encoding.UTF8, leaveOpen: true); - - while (true) - { - var i = reader.Read(); - - if (i < 0) - { - break; - } - - writer.Write((char)i); - } + using var writer = new StreamWriter(stream, encoding ?? Encoding.Default); + ToJson(writer, options, serialize); } - private static void ToJson(Stream stream, Action serialize) + private static void ToJson(TextWriter writer, SerializerOptions? options, Action serialize) { - using var serializer = new LossySerializer(stream); + using var serializer = new LossySerializer(writer, options); serialize(serializer); } - private sealed class LossySerializer(Stream stream) : IDisposable + private sealed class LossySerializer(TextWriter writer, SerializerOptions? options, bool leaveOpen = false) : IDisposable { - private readonly Utf8JsonWriter writer = new(stream, new() { Indented = true, SkipValidation = true }); + private readonly JsonWriter writer = new(writer, options, leaveOpen); public void ToJson(INbtTag tag) { @@ -390,7 +356,7 @@ public static class JsonNbt foreach (var entry in tag) { - ToJson(entry); + writer.WriteElement(entry, ToJson); } writer.WriteEndArray(); @@ -402,8 +368,7 @@ public static class JsonNbt foreach (var entry in tag as IEnumerable) { - writer.WritePropertyName(entry.Name); - ToJson(entry.Tag); + writer.WriteProperty(entry.Name, entry.Tag, ToJson); } writer.WriteEndObject(); @@ -479,7 +444,7 @@ public static class JsonNbt foreach (var e in tag) { - writeValue(e); + writer.WriteElement(e, writeValue); } writer.WriteEndArray(); @@ -487,13 +452,13 @@ public static class JsonNbt private static void ToJson(INbtValue tag, Action writeValue) where T : notnull => writeValue(tag.Value); - private void ToJson(sbyte value) => writer.WriteNumberValue(value); - private void ToJson(short value) => writer.WriteNumberValue(value); - private void ToJson(int value) => writer.WriteNumberValue(value); - private void ToJson(long value) => writer.WriteNumberValue(value); - private void ToJson(float value) => writer.WriteNumberValue(value); - private void ToJson(double value) => writer.WriteNumberValue(value); - private void ToJson(string value) => writer.WriteStringValue(value); + private void ToJson(sbyte value) => writer.WriteValue(value); + private void ToJson(short value) => writer.WriteValue(value); + private void ToJson(int value) => writer.WriteValue(value); + private void ToJson(long value) => writer.WriteValue(value); + private void ToJson(float value) => writer.WriteValue(value); + private void ToJson(double value) => writer.WriteValue(value); + private void ToJson(string value) => writer.WriteValue(value); public void Dispose() { @@ -505,6 +470,192 @@ public static class JsonNbt #endregion + private sealed class JsonWriter(TextWriter writer, SerializerOptions? options, bool leaveOpen = false) : IDisposable + { + private readonly TextWriter writer = writer; + private readonly SerializerOptions options = options ?? SerializerOptions.Default; + private readonly bool leaveOpen = leaveOpen; + private int depth = 0; + private Context context = new(null, 0); + + public void WriteStartArray() + { + writer.Write('['); + + depth++; + context = new(context, 1); + } + + public void WriteEndArray() + { + depth--; + + if (context.length > 0 && options.Style is SerializationStyle.Indented) + { + writer.WriteLine(); + WriteIndent(); + } + + context = context.parent!; + + writer.Write(']'); + } + + public void WriteStartObject() + { + writer.Write('{'); + + depth++; + context = new(context, 2); + } + + public void WriteEndObject() + { + depth--; + + if (context.length > 0 && options.Style is SerializationStyle.Indented) + { + writer.WriteLine(); + WriteIndent(); + } + + context = context.parent!; + + writer.Write('}'); + } + + public void WriteElement(byte value) => WriteElement(value, WriteValue); + public void WriteElement(sbyte value) => WriteElement(value, WriteValue); + public void WriteElement(short value) => WriteElement(value, WriteValue); + public void WriteElement(ushort value) => WriteElement(value, WriteValue); + public void WriteElement(int value) => WriteElement(value, WriteValue); + public void WriteElement(uint value) => WriteElement(value, WriteValue); + public void WriteElement(long value) => WriteElement(value, WriteValue); + public void WriteElement(ulong value) => WriteElement(value, WriteValue); + public void WriteElement(float value) => WriteElement(value, WriteValue); + public void WriteElement(double value) => WriteElement(value, WriteValue); + public void WriteElement(string value) => WriteElement(value, WriteValue); + + public void WriteElement(T value, Action elementWriter) + { + WriteSeparator(); + + elementWriter(value); + + context.length++; + } + + public void WriteProperty(string name, byte value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, sbyte value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, short value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, ushort value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, int value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, uint value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, long value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, ulong value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, float value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, double value) => WriteProperty(name, value, WriteValue); + public void WriteProperty(string name, string value) => WriteProperty(name, value, WriteValue); + + public void WritePropertyName(string name) + { + WriteSeparator(); + + WriteValue(name); + + writer.Write(':'); + if (options.Style is not SerializationStyle.Compact) + { + writer.Write(' '); + } + } + + public void WriteProperty(string name, T value, Action valueWriter) + { + WritePropertyName(name); + + valueWriter(value); + + context.length++; + } + + public void WriteValue(byte value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(sbyte value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(short value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(ushort value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(int value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(uint value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(long value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(ulong value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(float value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(double value) => writer.Write(value.ToString(CultureInfo.InvariantCulture)); + public void WriteValue(string value) => Utils.Quote(writer, value, '"', '\\'); + + private void WriteSeparator() + { + if (context.length > 0) + { + writer.Write(','); + if (options.Style is SerializationStyle.Spaced) + { + writer.Write(' '); + } + } + + if (options.Style is SerializationStyle.Indented) + { + writer.WriteLine(); + WriteIndent(); + } + } + + private void WriteIndent() + { + for (var i = 0; i < depth; i++) + { + writer.Write(options.Indent); + } + } + + public void Dispose() + { + if (!leaveOpen) + { + writer.Dispose(); + } + + GC.SuppressFinalize(this); + } + + private class Context(Context? parent, byte type) + { + public readonly Context? parent = parent; + public readonly byte type = type; + public int length = 0; + } + } + + private readonly struct NbtObjectMeta : IDisposable + { + private readonly JsonWriter writer; + + public NbtObjectMeta(JsonWriter writer, INbtTag tag) + { + this.writer = writer; + + writer.WriteStartObject(); + writer.WriteProperty(TypeProperty.Value, (int)tag.Type); + writer.WritePropertyName(ValueProperty.Value); + } + + public void Dispose() + { + writer.WriteEndObject(); + } + } + + #endregion + #region Deserialize public static INbtTag Deserialize(string s)