diff --git a/ToonFormat.slnx b/ToonFormat.slnx new file mode 100644 index 0000000..4dd3dcb --- /dev/null +++ b/ToonFormat.slnx @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/ToonFormat/Constants.cs b/src/ToonFormat/Constants.cs index a16ec88..2505013 100644 --- a/src/ToonFormat/Constants.cs +++ b/src/ToonFormat/Constants.cs @@ -78,7 +78,7 @@ public static bool IsStructural(char c) /// TOON's unified options configuration, styled to align with System.Text.Json. Used to control indentation, /// delimiters, strict mode, length markers, and underlying JSON behavior. /// - public enum ToonDelimiter + public enum ToonDelimiter : byte { /// Comma , COMMA, diff --git a/src/ToonFormat/Internal/Decode/Scanner.cs b/src/ToonFormat/Internal/Decode/Scanner.cs index b14c27d..b328d3e 100644 --- a/src/ToonFormat/Internal/Decode/Scanner.cs +++ b/src/ToonFormat/Internal/Decode/Scanner.cs @@ -1,7 +1,6 @@ #nullable enable using System; using System.Collections.Generic; -using System.Linq; namespace ToonFormat.Internal.Decode { @@ -10,7 +9,6 @@ namespace ToonFormat.Internal.Decode /// internal class ParsedLine { - public string Raw { get; set; } = string.Empty; public int Indent { get; set; } public string Content { get; set; } = string.Empty; public int Depth { get; set; } @@ -188,7 +186,6 @@ public static ScanResult ToParsedLines(string source, int indentSize, bool stric } parsed.Add(new ParsedLine { - Raw = new string(lineSpan), Indent = indent, Content = new string(contentSpan), Depth = lineDepth, diff --git a/src/ToonFormat/Internal/Encode/LineWriter.cs b/src/ToonFormat/Internal/Encode/LineWriter.cs index fb3409f..1fe76cb 100644 --- a/src/ToonFormat/Internal/Encode/LineWriter.cs +++ b/src/ToonFormat/Internal/Encode/LineWriter.cs @@ -10,8 +10,10 @@ namespace ToonFormat.Internal.Encode /// internal class LineWriter { - private readonly List _lines = new(); - private readonly string _indentationString; + private readonly StringBuilder _builder = new(); + private readonly string _indentationUnit; + private readonly List _indentCache = new() { string.Empty }; + private bool _hasAnyLine; /// /// Creates a new LineWriter with the specified indentation size. @@ -19,7 +21,7 @@ internal class LineWriter /// Number of spaces per indentation level. public LineWriter(int indentSize) { - _indentationString = new string(' ', indentSize); + _indentationUnit = new string(' ', indentSize); } /// @@ -29,8 +31,17 @@ public LineWriter(int indentSize) /// The content of the line. public void Push(int depth, string content) { - var indent = RepeatString(_indentationString, depth); - _lines.Add(indent + content); + if (_hasAnyLine) + { + _builder.Append('\n'); + } + else + { + _hasAnyLine = true; + } + + _builder.Append(GetIndent(depth)); + _builder.Append(content); } /// @@ -48,23 +59,20 @@ public void PushListItem(int depth, string content) /// public override string ToString() { - return string.Join("\n", _lines); + return _builder.ToString(); } - /// - /// Helper method to repeat a string n times. - /// - private static string RepeatString(string str, int count) + private string GetIndent(int depth) { - if (count <= 0) + if (depth <= 0) return string.Empty; - var sb = new StringBuilder(str.Length * count); - for (int i = 0; i < count; i++) + while (_indentCache.Count <= depth) { - sb.Append(str); + _indentCache.Add(string.Concat(_indentCache[^1], _indentationUnit)); } - return sb.ToString(); + + return _indentCache[depth]; } } } diff --git a/src/ToonFormat/Internal/Encode/Normalize.cs b/src/ToonFormat/Internal/Encode/Normalize.cs index a9f2b2c..1ac32c3 100644 --- a/src/ToonFormat/Internal/Encode/Normalize.cs +++ b/src/ToonFormat/Internal/Encode/Normalize.cs @@ -102,8 +102,10 @@ internal static class Normalize var type = value.GetType(); var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); - foreach (var prop in properties.Where(prop => prop.CanRead)) + foreach (var prop in properties) { + if (!prop.CanRead) + continue; var propValue = prop.GetValue(value); jsonObject[prop.Name] = NormalizeValue(propValue); } @@ -211,7 +213,7 @@ internal static class Normalize /// /// Determines if a value is a plain object (not a primitive, collection, or special type). /// - private static bool IsPlainObject(object value) + private static bool IsPlainObject(object? value) { if (value == null) return false; diff --git a/src/ToonFormat/Internal/Encode/Primitives.cs b/src/ToonFormat/Internal/Encode/Primitives.cs index ccb9016..ad688b2 100644 --- a/src/ToonFormat/Internal/Encode/Primitives.cs +++ b/src/ToonFormat/Internal/Encode/Primitives.cs @@ -94,8 +94,17 @@ public static string EncodeKey(string key) /// public static string EncodeAndJoinPrimitives(IEnumerable values, char delimiter = Constants.COMMA) { - var encoded = values.Select(v => EncodePrimitive(v, delimiter)); - return string.Join(delimiter.ToString(), encoded); + var sb = new StringBuilder(); + bool first = true; + foreach (var value in values) + { + if (!first) + sb.Append(delimiter); + first = false; + + sb.Append(EncodePrimitive(value, delimiter)); + } + return sb.ToString(); } // #endregion @@ -116,32 +125,36 @@ public static string FormatHeader( char? delimiter = null) { var delimiterChar = delimiter ?? Constants.DEFAULT_DELIMITER_CHAR; - var header = string.Empty; + var sb = new StringBuilder(); // Add key if present if (!string.IsNullOrEmpty(key)) - { - header += EncodeKey(key); - } + sb.Append(EncodeKey(key)); // Add array length with optional marker and delimiter - var delimiterSuffix = delimiterChar != Constants.DEFAULT_DELIMITER_CHAR - ? delimiterChar.ToString() - : string.Empty; - - header += $"{Constants.OPEN_BRACKET}{length}{delimiterSuffix}{Constants.CLOSE_BRACKET}"; + sb.Append(Constants.OPEN_BRACKET); + if (lengthMarker) + sb.Append(Constants.HASH); + sb.Append(length); + if (delimiterChar != Constants.DEFAULT_DELIMITER_CHAR) + sb.Append(delimiterChar); + sb.Append(Constants.CLOSE_BRACKET); // Add field names for tabular format - if (fields != null && fields.Count > 0) + if (fields is { Count: > 0 }) { - var quotedFields = fields.Select(EncodeKey); - var fieldsStr = string.Join(delimiterChar.ToString(), quotedFields); - header += $"{Constants.OPEN_BRACE}{fieldsStr}{Constants.CLOSE_BRACE}"; + sb.Append(Constants.OPEN_BRACE); + for (int i = 0; i < fields.Count; i++) + { + if (i > 0) + sb.Append(delimiterChar); + sb.Append(EncodeKey(fields[i])); + } + sb.Append(Constants.CLOSE_BRACE); } - header += Constants.COLON; - - return header; + sb.Append(Constants.COLON); + return sb.ToString(); } // #endregion diff --git a/src/ToonFormat/ToonDecoder.cs b/src/ToonFormat/ToonDecoder.cs index dc1551f..e393279 100644 --- a/src/ToonFormat/ToonDecoder.cs +++ b/src/ToonFormat/ToonDecoder.cs @@ -99,6 +99,54 @@ public static class ToonDecoder return JsonSerializer.Deserialize(json); } + /// + /// Decodes TOON data from a UTF-8 byte span into a JsonNode with default options. + /// + /// UTF-8 encoded TOON text. + public static JsonNode? Decode(ReadOnlySpan utf8Bytes) + { + return Decode(utf8Bytes, new ToonDecodeOptions()); + } + + /// + /// Decodes TOON data from a UTF-8 byte span into a JsonNode with custom options. + /// + /// UTF-8 encoded TOON text. + /// Decoding options to customize parsing behavior. + public static JsonNode? Decode(ReadOnlySpan utf8Bytes, ToonDecodeOptions? options) + { + if (options == null) + throw new ArgumentNullException(nameof(options)); + + var text = Encoding.UTF8.GetString(utf8Bytes); + return Decode(text, options); + } + + /// + /// Decodes TOON data from a UTF-8 byte span into the specified type with default options. + /// + /// Target type to deserialize into. + /// UTF-8 encoded TOON text. + public static T? Decode(ReadOnlySpan utf8Bytes) + { + return Decode(utf8Bytes, new ToonDecodeOptions()); + } + + /// + /// Decodes TOON data from a UTF-8 byte span into the specified type with custom options. + /// + /// Target type to deserialize into. + /// UTF-8 encoded TOON text. + /// Decoding options to customize parsing behavior. + public static T? Decode(ReadOnlySpan utf8Bytes, ToonDecodeOptions? options) + { + if (options == null) + throw new ArgumentNullException(nameof(options)); + + var text = Encoding.UTF8.GetString(utf8Bytes); + return Decode(text, options); + } + /// /// Decodes TOON data from a UTF-8 byte array into a JsonNode with default options. /// diff --git a/src/ToonFormat/ToonSerializer.cs b/src/ToonFormat/ToonSerializer.cs new file mode 100644 index 0000000..ddeb552 --- /dev/null +++ b/src/ToonFormat/ToonSerializer.cs @@ -0,0 +1,199 @@ +using System; +using System.IO; +using System.Text.Json.Nodes; +using Toon.Format; + +namespace ToonFormat; + +/// +/// Provides a concise API similar to System.Text.Json.JsonSerializer for converting between TOON format and objects/JSON. +/// +public static class ToonSerializer +{ + #region Serialize (Object to TOON) + + /// + /// Serializes the specified value to a TOON-formatted string. + /// + /// The type of the value to serialize. + /// The value to serialize. + /// A TOON-formatted string representation of the value. + public static string Serialize(T value) + { + return ToonEncoder.Encode(value); + } + + /// + /// Serializes the specified value to a TOON-formatted string with custom options. + /// + /// The type of the value to serialize. + /// The value to serialize. + /// Options to control serialization behavior. + /// A TOON-formatted string representation of the value. + public static string Serialize(T value, ToonEncodeOptions? options) + { + return ToonEncoder.Encode(value, options ?? new ToonEncodeOptions()); + } + + /// + /// Serializes the specified value to a UTF-8 encoded TOON byte array. + /// + /// The type of the value to serialize. + /// The value to serialize. + /// A UTF-8 encoded byte array containing the TOON representation. + public static byte[] SerializeToUtf8Bytes(T value) + { + return ToonEncoder.EncodeToBytes(value); + } + + /// + /// Serializes the specified value to a UTF-8 encoded TOON byte array with custom options. + /// + /// The type of the value to serialize. + /// The value to serialize. + /// Options to control serialization behavior. + /// A UTF-8 encoded byte array containing the TOON representation. + public static byte[] SerializeToUtf8Bytes(T value, ToonEncodeOptions? options) + { + return ToonEncoder.EncodeToBytes(value, options ?? new ToonEncodeOptions()); + } + + /// + /// Serializes the specified value and writes it to the specified stream. + /// + /// The type of the value to serialize. + /// The stream to write the TOON representation to. + /// The value to serialize. + public static void Serialize(Stream stream, T value) + { + ToonEncoder.EncodeToStream(value, stream); + } + + /// + /// Serializes the specified value and writes it to the specified stream with custom options. + /// + /// The type of the value to serialize. + /// The stream to write the TOON representation to. + /// The value to serialize. + /// Options to control serialization behavior. + public static void Serialize(Stream stream, T value, ToonEncodeOptions? options) + { + ToonEncoder.EncodeToStream(value, stream, options ?? new ToonEncodeOptions()); + } + + #endregion + + #region Deserialize (TOON to Object) + + /// + /// Deserializes the TOON-formatted string to the specified type. + /// + /// The type to deserialize to. + /// The TOON-formatted string to deserialize. + /// The deserialized value of type T. + /// Thrown when toon is null. + /// Thrown when the TOON format is invalid. + public static T? Deserialize(string toon) + { + return ToonDecoder.Decode(toon); + } + + /// + /// Deserializes the TOON-formatted string to the specified type with custom options. + /// + /// The type to deserialize to. + /// The TOON-formatted string to deserialize. + /// Options to control deserialization behavior. + /// The deserialized value of type T. + /// Thrown when toon or options is null. + /// Thrown when the TOON format is invalid. + public static T? Deserialize(string toon, ToonDecodeOptions? options) + { + return ToonDecoder.Decode(toon, options ?? new ToonDecodeOptions()); + } + + /// + /// Deserializes the UTF-8 encoded TOON byte array to the specified type. + /// + /// The type to deserialize to. + /// The UTF-8 encoded TOON byte array to deserialize. + /// The deserialized value of type T. + /// Thrown when utf8Toon is null. + /// Thrown when the TOON format is invalid. + public static T? Deserialize(ReadOnlySpan utf8Toon) + { + return ToonDecoder.Decode(utf8Toon); + } + + /// + /// Deserializes the UTF-8 encoded TOON byte array to the specified type with custom options. + /// + /// The type to deserialize to. + /// The UTF-8 encoded TOON byte array to deserialize. + /// Options to control deserialization behavior. + /// The deserialized value of type T. + /// Thrown when options is null. + /// Thrown when the TOON format is invalid. + public static T? Deserialize(ReadOnlySpan utf8Toon, ToonDecodeOptions? options) + { + return ToonDecoder.Decode(utf8Toon, options ?? new ToonDecodeOptions()); + } + + /// + /// Deserializes the TOON data from the specified stream to the specified type. + /// + /// The type to deserialize to. + /// The stream containing TOON data to deserialize. + /// The deserialized value of type T. + /// Thrown when stream is null. + /// Thrown when the TOON format is invalid. + public static T? Deserialize(Stream stream) + { + return ToonDecoder.Decode(stream); + } + + /// + /// Deserializes the TOON data from the specified stream to the specified type with custom options. + /// + /// The type to deserialize to. + /// The stream containing TOON data to deserialize. + /// Options to control deserialization behavior. + /// The deserialized value of type T. + /// Thrown when stream or options is null. + /// Thrown when the TOON format is invalid. + public static T? Deserialize(Stream stream, ToonDecodeOptions? options) + { + return ToonDecoder.Decode(stream, options ?? new ToonDecodeOptions()); + } + + #endregion + + #region JsonNode Conversion + + /// + /// Deserializes the TOON-formatted string to a JsonNode. + /// + /// The TOON-formatted string to deserialize. + /// The deserialized JsonNode. + /// Thrown when toon is null. + /// Thrown when the TOON format is invalid. + public static JsonNode? DeserializeToJsonNode(string toon) + { + return ToonDecoder.Decode(toon); + } + + /// + /// Deserializes the TOON-formatted string to a JsonNode with custom options. + /// + /// The TOON-formatted string to deserialize. + /// Options to control deserialization behavior. + /// The deserialized JsonNode. + /// Thrown when toon or options is null. + /// Thrown when the TOON format is invalid. + public static JsonNode? DeserializeToJsonNode(string toon, ToonDecodeOptions? options) + { + return ToonDecoder.Decode(toon, options ?? new ToonDecodeOptions()); + } + + #endregion +} \ No newline at end of file