Removed SerializableAttribute and ISerializable interface from tags. Improved JSON output.

This commit is contained in:
Eric Freed 2023-08-27 15:27:44 -04:00
parent 5d974eeeb4
commit 11f3c4d70c
17 changed files with 317 additions and 329 deletions

View File

@ -2,8 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using JetBrains.Annotations;
@ -13,7 +11,7 @@ namespace SharpNBT;
/// Base class for NBT tags that contain a fixed-size array of numeric types.
/// </summary>
/// <typeparam name="T">A value type that implements <see cref="INumber{TSelf}"/>.</typeparam>
[PublicAPI][Serializable]
[PublicAPI]
public abstract class ArrayTag<T> : Tag, IReadOnlyList<T> where T : unmanaged, INumber<T>
{
/// <summary>
@ -35,22 +33,6 @@ public abstract class ArrayTag<T> : Tag, IReadOnlyList<T> where T : unmanaged, I
array = value;
}
/// <inheritdoc />
protected ArrayTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var _ = info.GetInt32("count");
var value = info.GetValue("values", typeof(T[])) as T[];
array = value ?? Array.Empty<T>();
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("count", array.Length);
info.AddValue("values", array);
}
/// <inheritdoc />
public IEnumerator<T> GetEnumerator()
{

View File

@ -1,5 +1,5 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -11,7 +11,7 @@ namespace SharpNBT;
/// This tag type does not exist in the NBT specification, and is included for convenience to differentiate it from the <see cref="ByteTag"/> that it is
/// actually serialized as.
/// </remarks>
[PublicAPI][Serializable]
[PublicAPI]
[Obsolete("Use the IsBool and Bool properties of ByteTag. This class will be removed in a future version.")]
public class BoolTag : Tag
{
@ -26,16 +26,20 @@ public class BoolTag : Tag
{
Value = value;
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected BoolTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteBoolean(Name, Value);
}
else
{
writer.WriteBooleanValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Byte({PrettyName}): {(Value ? "true" : "false")}";

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -13,7 +13,7 @@ namespace SharpNBT;
/// While this class uses the CLS compliant <see cref="byte"/> (0..255), the NBT specification uses a signed value with a range of -128..127, so ensure
/// the bits are equivalent for your values.
/// </remarks>
[PublicAPI][Serializable]
[PublicAPI]
public class ByteArrayTag : ArrayTag<byte>
{
/// <summary>
@ -51,16 +51,24 @@ public class ByteArrayTag : ArrayTag<byte>
public ByteArrayTag(string? name, ReadOnlySpan<byte> values) : base(TagType.ByteArray, name, values.ToArray())
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected ByteArrayTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteStartArray(Name);
}
else
{
writer.WriteStartArray();
}
for (var i = 0; i < Count; i++)
writer.WriteNumberValue(this[i]);
writer.WriteEndArray();
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
{

View File

@ -1,5 +1,5 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -86,15 +86,6 @@ public class ByteTag : NumericTag<byte>
public ByteTag(string? name, sbyte value) : base(TagType.Byte, name, unchecked((byte) value))
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected ByteTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
@ -102,6 +93,22 @@ public class ByteTag : NumericTag<byte>
object obj = IsBool ? Bool : Value;
return $"TAG_Byte({PrettyName}): {obj}";
}
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
if (IsBool)
writer.WriteBoolean(Name, Bool);
else
writer.WriteNumber(Name, Value);
}
else
{
writer.WriteNumberValue(Value);
}
}
/// <summary>
/// Implicit conversion of this tag to a <see cref="byte"/>.

View File

@ -3,8 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -16,7 +16,7 @@ namespace SharpNBT;
/// This along with the <see cref="ListTag"/> class define the structure of the NBT format. Children are not order-dependent, nor is order guaranteed. The
/// closing <see cref="EndTag"/> does not require to be explicitly added, it will be added automatically during serialization.
/// </remarks>
[PublicAPI][Serializable]
[PublicAPI]
public class CompoundTag : Tag, IDictionary<string, Tag>, ICollection<Tag>
{
private readonly Dictionary<string, Tag> dict;
@ -42,25 +42,7 @@ public class CompoundTag : Tag, IDictionary<string, Tag>, ICollection<Tag>
dict.Add(value.Name!, AssertName(value));
}
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected CompoundTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var result = info.GetValue("children", typeof(Dictionary<string, Tag>)) as Dictionary<string, Tag>;
dict = result ?? new Dictionary<string, Tag>();
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("children", dict);
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, Tag>>.Add(KeyValuePair<string, Tag> item) => dict.Add(item.Key, item.Value);
@ -157,6 +139,23 @@ public class CompoundTag : Tag, IDictionary<string, Tag>, ICollection<Tag>
return (TTag)dict[name];
}
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteStartObject(Name);
}
else
{
writer.WriteStartObject();
}
foreach (var child in dict.Values)
child.WriteJson(writer, true);
writer.WriteEndObject();
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.ToString?view=netcore-5.0">`Object.ToString` on docs.microsoft.com</a></footer>
@ -184,18 +183,18 @@ public class CompoundTag : Tag, IDictionary<string, Tag>, ICollection<Tag>
/// <param name="name">The name of the tag to search for.</param>
/// <param name="recursive"><see langword="true"/> to recursively search children, otherwise <see langword="false"/> to only search direct descendants.</param>
/// <returns>The first tag found with <paramref name="name"/>, otherwise <see langword="null"/> if none was found.</returns>
public Tag? Find(string name, bool recursive = false)
public TTag? Find<TTag>(string name, bool recursive = false) where TTag : Tag
{
foreach (var tag in dict.Values)
foreach (var (key, value) in dict)
{
if (string.CompareOrdinal(name, tag.Name) == 0)
return tag;
if (string.CompareOrdinal(name, key) == 0 && value is TTag result)
return result;
if (recursive && tag is CompoundTag child)
if (recursive && value is CompoundTag child)
{
var result = child.Find(name, true);
if (result != null)
return result;
var nested = child.Find<TTag>(name, recursive);
if (nested != null)
return nested;
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -7,7 +6,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that contains a single IEEE-754 double-precision floating point number.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class DoubleTag : NumericTag<double>
{
/// <summary>
@ -18,14 +17,18 @@ public class DoubleTag : NumericTag<double>
public DoubleTag(string? name, double value) : base(TagType.Double, name, value)
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected DoubleTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteNumber(Name, Value);
}
else
{
writer.WriteNumberValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -1,4 +1,5 @@
using System.Text;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -15,7 +16,13 @@ public sealed class EndTag : Tag
public EndTag() : base(TagType.End, null)
{
}
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
// Do nothing
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_End";

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -7,7 +6,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that contains a single IEEE-754 single-precision floating point number.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class FloatTag : NumericTag<float>
{
/// <summary>
@ -18,13 +17,18 @@ public class FloatTag : NumericTag<float>
public FloatTag(string? name, float value) : base(TagType.Float, name, value)
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected FloatTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteNumber(Name, Value);
}
else
{
writer.WriteNumberValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -9,7 +10,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that whose value is a contiguous sequence of 32-bit integers.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class IntArrayTag : ArrayTag<int>
{
/// <summary>
@ -48,14 +49,22 @@ public class IntArrayTag : ArrayTag<int>
public IntArrayTag(string? name, ReadOnlySpan<int> values) : base(TagType.IntArray, name, values.ToArray())
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected IntArrayTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteStartArray(Name);
}
else
{
writer.WriteStartArray();
}
for (var i = 0; i < Count; i++)
writer.WriteNumberValue(this[i]);
writer.WriteEndArray();
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -1,5 +1,5 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -7,7 +7,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that contains a single 32-bit integer value.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class IntTag : NumericTag<int>
{
/// <summary>
@ -37,14 +37,18 @@ public class IntTag : NumericTag<int>
public IntTag(Tag? parent, string? name, uint value) : base(TagType.Int, name, unchecked((int) value))
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected IntTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteNumber(Name, Value);
}
else
{
writer.WriteNumberValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -2,8 +2,8 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -14,7 +14,7 @@ namespace SharpNBT;
/// <remarks>
/// All child tags <b>must</b> be have the same <see cref="Tag.Type"/> value, and their <see cref="Tag.Name"/> value will be omitted during serialization.
/// </remarks>
[PublicAPI][Serializable]
[PublicAPI]
public class ListTag : Tag, IList<Tag>
{
/// <summary>
@ -56,28 +56,6 @@ public class ListTag : Tag, IList<Tag>
foreach (var item in children)
list.Add(AssertType(item));
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected ListTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
ChildType = (TagType)info.GetByte("child_type");
var count = info.GetInt32("count");
list = new List<Tag>(count);
if (info.GetValue("values", typeof(Tag[])) is Tag[] ary)
AddRange(ary);
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("child_type", (byte) ChildType);
info.AddValue("count", list.Count);
info.AddValue("values", list.ToArray());
}
/// <inheritdoc />
public IEnumerator<Tag> GetEnumerator() => list.GetEnumerator();
@ -128,24 +106,22 @@ public class ListTag : Tag, IList<Tag>
set => list[index] = AssertType(value);
}
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteStartArray(Name);
}
else
{
writer.WriteStartArray();
}
for (var i = 0; i < Count; i++)
list[i].WriteJson(writer, false);
writer.WriteEndArray();
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
@ -153,9 +129,7 @@ public class ListTag : Tag, IList<Tag>
var word = Count == 1 ? Strings.WordEntry : Strings.WordEntries;
return $"TAG_List({PrettyName}): [{Count} {word}]";
}
/// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/>
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -9,7 +9,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that whose value is a contiguous sequence of 64-bit integers.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class LongArrayTag : ArrayTag<long>
{
/// <summary>
@ -29,15 +29,6 @@ public class LongArrayTag : ArrayTag<long>
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected LongArrayTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LongArrayTag"/> with the specified <paramref name="values"/>.
/// </summary>
@ -55,6 +46,23 @@ public class LongArrayTag : ArrayTag<long>
public LongArrayTag(string? name, ReadOnlySpan<long> values) : base(TagType.LongArray, name, values.ToArray())
{
}
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteStartArray(Name);
}
else
{
writer.WriteStartArray();
}
for (var i = 0; i < Count; i++)
writer.WriteNumberValue(this[i]);
writer.WriteEndArray();
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()

View File

@ -1,5 +1,5 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -7,7 +7,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that contains a single 64-bit integer value.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class LongTag : NumericTag<long>
{
/// <summary>
@ -37,14 +37,18 @@ public class LongTag : NumericTag<long>
public LongTag(string? name, ulong value) : base(TagType.Long, name, unchecked((long) value))
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected LongTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteNumber(Name, Value);
}
else
{
writer.WriteNumberValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -10,7 +10,7 @@ namespace SharpNBT;
/// Abstract base class for <see cref="Tag"/> types that contain a single numeric value.
/// </summary>
/// <typeparam name="T">A value type that implements <see cref="INumber{TSelf}"/>.</typeparam>
[PublicAPI][Serializable]
[PublicAPI]
public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparable<NumericTag<T>>, IComparable where T : unmanaged, INumber<T>
{
/// <summary>
@ -23,21 +23,7 @@ public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparabl
{
Value = value;
}
/// <inheritdoc />
protected NumericTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var value = info.GetValue("value", typeof(T));
Value = value is null ? default : (T)value;
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("value", Value, typeof(T));
}
/// <inheritdoc />
public bool Equals(NumericTag<T>? other)
{

View File

@ -1,5 +1,5 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -7,7 +7,7 @@ namespace SharpNBT;
/// <summary>
/// A tag that contains a single 16-bit integer value.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class ShortTag : NumericTag<short>
{
/// <summary>
@ -43,14 +43,18 @@ public class ShortTag : NumericTag<short>
public ShortTag(string? name, ushort value) : base(TagType.Short, name, unchecked((short) value))
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected ShortTag(SerializationInfo info, StreamingContext context) : base(info, context)
/// <inheritdoc />
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
if (named && Name != null)
{
writer.WriteNumber(Name, Value);
}
else
{
writer.WriteNumberValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -1,6 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.Serialization;
using System.Text.Json;
using JetBrains.Annotations;
namespace SharpNBT;
@ -8,7 +7,7 @@ namespace SharpNBT;
/// <summary>
/// A tag the contains a UTF-8 string.
/// </summary>
[PublicAPI][Serializable]
[PublicAPI]
public class StringTag : Tag, IEquatable<StringTag>
{
/// <summary>
@ -25,18 +24,18 @@ public class StringTag : Tag, IEquatable<StringTag>
{
Value = value ?? string.Empty;
}
/// <inheritdoc />
protected StringTag(SerializationInfo info, StreamingContext context) : base(info, context)
protected internal override void WriteJson(Utf8JsonWriter writer, bool named = true)
{
Value = info.GetString("value") ?? string.Empty;
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("value", Value);
if (named && Name != null)
{
writer.WriteString(Name, Value);
}
else
{
writer.WriteStringValue(Value);
}
}
/// <inheritdoc cref="object.ToString"/>

View File

@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations;
[assembly: CLSCompliant(true)]
@ -16,40 +15,9 @@ namespace SharpNBT;
/// <summary>
/// Abstract base class that all NBT tags inherit from.
/// </summary>
[PublicAPI][Serializable]
public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
[PublicAPI]
public abstract class Tag : IEquatable<Tag>, ICloneable
{
private static Regex simpleNameMatcher;
static Tag()
{
simpleNameMatcher = new Regex(@"^[A-Ba-z0-9_-]+$", RegexOptions.Compiled);
}
private static IEnumerable<Type> GetKnownTypes()
{
return new[]
{
typeof(TagType),
typeof(NumericTag<>),
typeof(ArrayTag<>),
typeof(Tag[]),
typeof(ByteTag),
typeof(ShortTag),
typeof(IntTag),
typeof(LongTag),
typeof(FloatTag),
typeof(DoubleTag),
typeof(StringTag),
typeof(ByteArrayTag),
typeof(IntArrayTag),
typeof(LongArrayTag),
typeof(ListTag),
typeof(CompoundTag)
};
}
/// <summary>
/// Text applied in a pretty-print sting when a tag has no defined <see cref="Name"/> value.
/// </summary>
@ -58,17 +26,18 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
/// <summary>
/// Gets a constant describing the NBT type this object represents.
/// </summary>
public TagType Type { get; private set; }
public TagType Type { get; }
/// <summary>
/// Gets the parent <see cref="Tag"/> this object is a child of.
/// </summary>
[Obsolete("Parent property will be removed in a future version.")]
public Tag? Parent { get; internal set; }
/// <summary>
/// Gets the name assigned to this <see cref="Tag"/>.
/// </summary>
public string? Name { get; set; }
public string? Name { get; }
/// <summary>
/// Initialized a new instance of the <see cref="Tag"/> class.
@ -98,77 +67,115 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
/// Gets the name of the object as a human-readable quoted string, or a default name to indicate it has no name when applicable.
/// </summary>
protected internal string PrettyName => Name is null ? "None" : $"\"{Name}\"";
/// <summary>
/// Uses the provided <paramref name="writer"/> to write the NBT tag in JSON format.
/// </summary>
/// <param name="writer">A JSON writer instance.</param>
/// <param name="named">
/// Flag indicating if this object's name should be written as a property name, or <see langword="false"/> when it
/// is a child of <see cref="ListTag"/>, in which case it should be written as a JSON array element.
/// </param>
protected internal abstract void WriteJson(Utf8JsonWriter writer, bool named = true);
/// <summary>
/// Writes the tag to the specified <paramref name="stream"/> in JSON format.
/// </summary>
/// <param name="stream">The stream instance to write to.</param>
/// <param name="options">Options that will be passed to the JSON writer.</param>
/// <exception cref="IOException">The stream is no opened for writing.</exception>
public void WriteJson(Stream stream, JsonWriterOptions? options = null)
{
using var json = new Utf8JsonWriter(stream, options ?? new JsonWriterOptions());
if (string.IsNullOrEmpty(Name))
{
json.WriteStartArray();
WriteJson(json, false);
json.WriteEndArray();
}
else
{
json.WriteStartObject();
WriteJson(json, true);
json.WriteEndObject();
}
json.Flush();
}
/// <summary>
/// Asynchronously writes the tag to the specified <paramref name="stream"/> in JSON format.
/// </summary>
/// <param name="stream">The stream instance to write to.</param>
/// <param name="options">Options that will be passed to the JSON writer.</param>
/// <exception cref="IOException">The stream is no opened for writing.</exception>
public async Task WriteJsonAsync(Stream stream, JsonWriterOptions? options = null)
{
await using var json = new Utf8JsonWriter(stream, options ?? new JsonWriterOptions());
if (string.IsNullOrEmpty(Name))
{
json.WriteStartArray();
WriteJson(json, false);
json.WriteEndArray();
}
else
{
json.WriteStartObject();
WriteJson(json, true);
json.WriteEndObject();
}
await json.FlushAsync();
}
/// <summary>
/// Converts the NBT to an equivalent JSON representation, and returns it as a string.
/// </summary>
/// <param name="options">Options that will be passed to the JSON writer.</param>
/// <returns>The JSON-encoded string representing describing the tag.</returns>
public string ToJson(JsonWriterOptions? options = null)
{
using var stream = new MemoryStream();
WriteJson(stream, options);
stream.Flush();
return Encoding.UTF8.GetString(stream.ToArray());
}
/// <summary>
/// Gets a representation of this <see cref="Tag"/> as a JSON string.
/// </summary>
/// <param name="pretty">Flag indicating if formatting should be applied to make the string human-readable.</param>
/// <param name="indent">When <paramref name="pretty"/> is <see lawnword="true"/>, indicates the indent characters(s) to use.</param>
/// <param name="indent">Ignored</param>
/// <returns>A JSON string describing this object.</returns>
[Obsolete("Use WriteJson and ToJson instead.")]
public string ToJsonString(bool pretty = false, string indent = " ")
{
var settings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true,
EmitTypeInformation = EmitTypeInformation.Never,
KnownTypes = GetKnownTypes()
};
var serializer = new DataContractJsonSerializer(typeof(Tag), settings);
using var stream = new MemoryStream();
if (pretty)
{
using var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, false, true, indent);
serializer.WriteObject(writer, this);
writer.Flush();
}
else
{
serializer.WriteObject(stream, this);
}
stream.Flush();
return Encoding.UTF8.GetString(stream.ToArray());
var options = new JsonWriterOptions { Indented = pretty };
return ToJson(options);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <see langword="true" /> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IEquatable-1.Equals?view=netstandard-2.1">`IEquatable.Equals` on docs.microsoft.com</a></footer>
/// <inheritdoc />
public bool Equals(Tag? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Type == other.Type && Name == other.Name;
return Type == other.Type && string.CompareOrdinal(Name, other.Name) == 0;
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns>
/// <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.Equals?view=netstandard-2.1">`Object.Equals` on docs.microsoft.com</a></footer>
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Tag)obj);
return obj.GetType() == GetType() && Equals((Tag)obj);
}
/// <summary>Serves as the default hash function.</summary>
/// <returns>A hash code for the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.GetHashCode?view=netstandard-2.1">`Object.GetHashCode` on docs.microsoft.com</a></footer>
public override int GetHashCode()
{
unchecked
{
// ReSharper disable NonReadonlyMemberInGetHashCode
return ((int)Type * 373) ^ (Name != null ? Name.GetHashCode() : 0);
// ReSharper restore NonReadonlyMemberInGetHashCode
}
}
/// <summary>Creates a new object that is a copy of the current instance.</summary>
/// <returns>A new object that is a copy of this instance.</returns>
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine((int)Type, Name);
/// <inheritdoc />
public object Clone()
{
// Serialize then deserialize to make a deep-copy
@ -182,28 +189,7 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
writer.WriteTag(this);
stream.Seek(0, SeekOrigin.Begin);
return reader.ReadTag(!(Parent is ListTag));
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected Tag(SerializationInfo info, StreamingContext context)
{
Type = (TagType) info.GetByte("type");
Name = info.GetString("name");
}
/// <summary>Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the target object.</summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to populate with data.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("type", (byte) Type);
info.AddValue("name", Name);
return reader.ReadTag(!string.IsNullOrWhiteSpace(Name));
}
/// <summary>
@ -238,7 +224,7 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
{
if (string.IsNullOrEmpty(Name))
return string.Empty;
return simpleNameMatcher.IsMatch(Name) ? Name : $"\"{Name}\"";
return Name.All(c => c.IsValidUnquoted()) ? Name : $"\"{Name}\"";
}
}
}