using System.Text; using System.Text.Json; using Nbt.Tag; namespace Nbt.Serialization; public static class JsonNbt { private static readonly JsonEncodedText TypeProperty = JsonEncodedText.Encode("type"); private static readonly JsonEncodedText ValueProperty = JsonEncodedText.Encode("value"); #region Serialize lossless 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)); 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 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)); private static string Serialize(Action serialize) { using var writer = new StringWriter(); Serialize(writer, serialize); return writer.ToString(); } private static void Serialize(TextWriter writer, 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); } } private static void Serialize(Stream stream, Action serialize) { using var serializer = new Serializer(stream); serialize(serializer); } private sealed class Serializer(Stream stream) : IDisposable { private readonly Utf8JsonWriter writer = new(stream, new() { Indented = true, SkipValidation = true }); public void Serialize(INbtTag tag) { switch (tag) { case INbtArray arrayTag: Serialize(arrayTag); break; case INbtValue valueTag: Serialize(valueTag); break; case INbtList listTag: Serialize(listTag); break; case INbtCompound compoundTag: Serialize(compoundTag); break; } } public void Serialize(INbtList tag) { using (new NbtObjectMeta(writer, tag.Type)) { writer.WriteStartArray(); foreach (var entry in tag) { Serialize(entry); } writer.WriteEndArray(); } } public void Serialize(INbtCompound tag) { using (new NbtObjectMeta(writer, tag.Type)) { writer.WriteStartObject(); foreach (var entry in tag as IEnumerable) { writer.WritePropertyName(entry.Name); Serialize(entry.Tag); } writer.WriteEndObject(); } } public void Serialize(INbtArray tag) { switch (tag) { case INbtArray byteArrayTag: Serialize(byteArrayTag); break; case INbtArray intArrayTag: Serialize(intArrayTag); break; case INbtArray longArrayTag: Serialize(longArrayTag); break; } } public void Serialize(INbtValue valueTag) { switch (valueTag) { case INbtValue tag: Serialize(tag); break; case INbtValue tag: Serialize(tag); break; case INbtValue tag: Serialize(tag); break; case INbtValue tag: Serialize(tag); break; case INbtValue tag: Serialize(tag); break; case INbtValue tag: Serialize(tag); break; case INbtValue tag: Serialize(tag); break; } } public void Serialize(INbtArray tag) => Serialize(tag, Serialize); public void Serialize(INbtArray tag) => Serialize(tag, Serialize); public void Serialize(INbtArray tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); public void Serialize(INbtValue tag) => Serialize(tag, Serialize); private void Serialize(INbtArray tag, Action writeValue) where T : notnull { using (new NbtObjectMeta(writer, tag.Type)) { writer.WriteStartArray(); foreach (var e in tag) { writeValue(e); } writer.WriteEndArray(); } } private void Serialize(INbtValue tag, Action writeValue) where T : notnull { using (new NbtObjectMeta(writer, tag.Type)) { 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); public void Dispose() { writer.Dispose(); 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 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 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(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)); private static string ToJson(Action serialize) { using var writer = new StringWriter(); ToJson(writer, serialize); return writer.ToString(); } private static void ToJson(TextWriter writer, 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); } } private static void ToJson(Stream stream, Action serialize) { using var serializer = new LossySerializer(stream); serialize(serializer); } private sealed class LossySerializer(Stream stream) : IDisposable { private readonly Utf8JsonWriter writer = new(stream, new() { Indented = true, SkipValidation = true }); public void ToJson(INbtTag tag) { switch (tag) { case INbtArray arrayTag: ToJson(arrayTag); break; case INbtValue valueTag: ToJson(valueTag); break; case INbtList listTag: ToJson(listTag); break; case INbtCompound compoundTag: ToJson(compoundTag); break; } } public void ToJson(INbtList tag) { writer.WriteStartArray(); foreach (var entry in tag) { ToJson(entry); } writer.WriteEndArray(); } public void ToJson(INbtCompound tag) { writer.WriteStartObject(); foreach (var entry in tag as IEnumerable) { writer.WritePropertyName(entry.Name); ToJson(entry.Tag); } writer.WriteEndObject(); } public void ToJson(INbtArray tag) { switch (tag) { case INbtArray byteArrayTag: ToJson(byteArrayTag); break; case INbtArray intArrayTag: ToJson(intArrayTag); break; case INbtArray longArrayTag: ToJson(longArrayTag); break; } } public void ToJson(INbtValue valueTag) { switch (valueTag) { case INbtValue tag: ToJson(tag); break; case INbtValue tag: ToJson(tag); break; case INbtValue tag: ToJson(tag); break; case INbtValue tag: ToJson(tag); break; case INbtValue tag: ToJson(tag); break; case INbtValue tag: ToJson(tag); break; case INbtValue tag: ToJson(tag); break; } } public void ToJson(INbtArray tag) => ToJson(tag, ToJson); public void ToJson(INbtArray tag) => ToJson(tag, ToJson); public void ToJson(INbtArray tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); public void ToJson(INbtValue tag) => ToJson(tag, ToJson); private void ToJson(INbtArray tag, Action writeValue) where T : notnull { writer.WriteStartArray(); foreach (var e in tag) { writeValue(e); } writer.WriteEndArray(); } 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); public void Dispose() { writer.Dispose(); GC.SuppressFinalize(this); } } #endregion #region Deserialize public static INbtTag Deserialize(string s) { using var reader = new StringReader(s); return Deserialize(reader); } public static INbtTag Deserialize(Stream stream) { var doc = JsonDocument.Parse(stream); return Deserialize(doc.RootElement); } public static INbtTag Deserialize(TextReader reader) { using var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true)) { while (true) { var i = reader.Read(); if (i < 0) { break; } writer.Write((char)i); } } stream.Position = 0L; return Deserialize(stream); } private static INbtTag Deserialize(JsonElement serialized) { var tagType = (NbtTagType)serialized.GetProperty(TypeProperty.Value).GetByte(); var value = serialized.GetProperty(ValueProperty.Value); switch (tagType) { case NbtTagType.End: throw new NbtException($"Invalid tag type: {Enum.GetName(tagType)}"); case NbtTagType.Byte: return new NbtByte(value.GetSByte()); case NbtTagType.Short: return new NbtShort(value.GetInt16()); case NbtTagType.Int: return new NbtInt(value.GetInt32()); case NbtTagType.Long: return new NbtLong(value.GetInt64()); case NbtTagType.Float: return new NbtFloat(value.GetSingle()); case NbtTagType.Double: return new NbtDouble(value.GetDouble()); case NbtTagType.String: return new NbtString(value.GetString() ?? throw new NbtException("Null string value")); case NbtTagType.ByteArray: { var bytes = new sbyte[value.GetArrayLength()]; var i = 0; foreach (var element in value.EnumerateArray()) { bytes[i++] = element.GetSByte(); } return new NbtByteArray(bytes); } case NbtTagType.IntArray: { var ints = new int[value.GetArrayLength()]; var i = 0; foreach (var element in value.EnumerateArray()) { ints[i++] = element.GetInt32(); } return new NbtIntArray(ints); } case NbtTagType.LongArray: { var longs = new long[value.GetArrayLength()]; var i = 0; foreach (var element in value.EnumerateArray()) { longs[i++] = element.GetInt64(); } return new NbtLongArray(longs); } case NbtTagType.List: { var list = new NbtList(); foreach (var element in value.EnumerateArray()) { list.Add(Deserialize(element)); } return list; } case NbtTagType.Compound: { var compound = new NbtCompound(); foreach (var property in value.EnumerateObject()) { compound.Add(property.Name, Deserialize(property.Value)); } return compound; } default: throw new UnknownTagTypeException(tagType); } } #endregion }