Coding style
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,415 +1,401 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using Unosquare.Swan.Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
/// <summary>
|
||||
/// A CSV writer useful for exporting a set of objects.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to save a list of objects into a CSV file.
|
||||
/// <code>
|
||||
/// using System.Collections.Generic;
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // create a list of people
|
||||
/// var people = new List<Person>
|
||||
/// {
|
||||
/// new Person { Name = "Artyom", Age = 20 },
|
||||
/// new Person { Name = "Aloy", Age = 18 }
|
||||
/// }
|
||||
///
|
||||
/// // write items inside file.csv
|
||||
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv");
|
||||
///
|
||||
/// // output
|
||||
/// // | Name | Age |
|
||||
/// // | Artyom | 20 |
|
||||
/// // | Aloy | 18 |
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class CsvWriter : IDisposable {
|
||||
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
|
||||
|
||||
private readonly Object _syncLock = new Object();
|
||||
private readonly Stream _outputStream;
|
||||
private readonly Encoding _encoding;
|
||||
private readonly Boolean _leaveStreamOpen;
|
||||
private Boolean _isDisposing;
|
||||
private UInt64 _mCount;
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// A CSV writer useful for exporting a set of objects.
|
||||
/// Initializes a new instance of the <see cref="CsvWriter" /> class.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to save a list of objects into a CSV file.
|
||||
/// <code>
|
||||
/// using System.Collections.Generic;
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // create a list of people
|
||||
/// var people = new List<Person>
|
||||
/// {
|
||||
/// new Person { Name = "Artyom", Age = 20 },
|
||||
/// new Person { Name = "Aloy", Age = 18 }
|
||||
/// }
|
||||
///
|
||||
/// // write items inside file.csv
|
||||
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv");
|
||||
///
|
||||
/// // output
|
||||
/// // | Name | Age |
|
||||
/// // | Artyom | 20 |
|
||||
/// // | Aloy | 18 |
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class CsvWriter : IDisposable
|
||||
{
|
||||
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private readonly Stream _outputStream;
|
||||
private readonly Encoding _encoding;
|
||||
private readonly bool _leaveStreamOpen;
|
||||
private bool _isDisposing;
|
||||
private ulong _mCount;
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(Stream outputStream, bool leaveOpen, Encoding encoding)
|
||||
{
|
||||
_outputStream = outputStream;
|
||||
_encoding = encoding;
|
||||
_leaveStreamOpen = leaveOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It automatically closes the stream when disposing this writer.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(Stream outputStream, Encoding encoding)
|
||||
: this(outputStream, false, encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It uses the Windows 1252 encoding and automatically closes
|
||||
/// the stream upon disposing this writer.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
public CsvWriter(Stream outputStream)
|
||||
: this(outputStream, false, Definitions.Windows1252Encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It opens the file given file, automatically closes the stream upon
|
||||
/// disposing of this writer, and uses the Windows 1252 encoding.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
public CsvWriter(string filename)
|
||||
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It opens the file given file, automatically closes the stream upon
|
||||
/// disposing of this writer, and uses the given text encoding for output.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(string filename, Encoding encoding)
|
||||
: this(File.OpenWrite(filename), false, encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the field separator character.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The separator character.
|
||||
/// </value>
|
||||
public char SeparatorCharacter { get; set; } = ',';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the escape character to use to escape field values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The escape character.
|
||||
/// </value>
|
||||
public char EscapeCharacter { get; set; } = '"';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the new line character sequence to use when writing a line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The new line sequence.
|
||||
/// </value>
|
||||
public string NewLineSequence { get; set; } = Environment.NewLine;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a list of properties to ignore when outputting CSV lines.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ignore property names.
|
||||
/// </value>
|
||||
public List<string> IgnorePropertyNames { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of lines that have been written, including the headings line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public ulong Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
return _mCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items to a stream.
|
||||
/// It uses the Windows 1252 text encoding for output.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
|
||||
/// <returns>Number of item saved.</returns>
|
||||
public static int SaveRecords<T>(IEnumerable<T> items, Stream stream, bool truncateData = false)
|
||||
{
|
||||
// truncate the file if it had data
|
||||
if (truncateData && stream.Length > 0)
|
||||
stream.SetLength(0);
|
||||
|
||||
using (var writer = new CsvWriter(stream))
|
||||
{
|
||||
writer.WriteHeadings<T>();
|
||||
writer.WriteObjects(items);
|
||||
return (int)writer.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items to a CSV file.
|
||||
/// If the file exits, it overwrites it. If it does not, it creates it.
|
||||
/// It uses the Windows 1252 text encoding for output.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="filePath">The file path.</param>
|
||||
/// <returns>Number of item saved.</returns>
|
||||
public static int SaveRecords<T>(IEnumerable<T> items, string filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generic, main Write Line Method
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text. Items are converted to strings.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// If items are not string, the ToStringInvariant() method is called on them.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(params object[] items)
|
||||
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text. Items are converted to strings.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// If items are not string, the ToStringInvariant() method is called on them.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(IEnumerable<object> items)
|
||||
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(params string[] items) => WriteLine((IEnumerable<string>) items);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(IEnumerable<string> items)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var length = items.Count();
|
||||
var separatorBytes = _encoding.GetBytes(new[] { SeparatorCharacter });
|
||||
var endOfLineBytes = _encoding.GetBytes(NewLineSequence);
|
||||
|
||||
// Declare state variables here to avoid recreation, allocation and
|
||||
// reassignment in every loop
|
||||
bool needsEnclosing;
|
||||
string textValue;
|
||||
byte[] output;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
textValue = items.ElementAt(i);
|
||||
|
||||
// Determine if we need the string to be enclosed
|
||||
// (it either contains an escape, new line, or separator char)
|
||||
needsEnclosing = textValue.IndexOf(SeparatorCharacter) >= 0
|
||||
|| textValue.IndexOf(EscapeCharacter) >= 0
|
||||
|| textValue.IndexOf('\r') >= 0
|
||||
|| textValue.IndexOf('\n') >= 0;
|
||||
|
||||
// Escape the escape characters by repeating them twice for every instance
|
||||
textValue = textValue.Replace($"{EscapeCharacter}",
|
||||
$"{EscapeCharacter}{EscapeCharacter}");
|
||||
|
||||
// Enclose the text value if we need to
|
||||
if (needsEnclosing)
|
||||
textValue = string.Format($"{EscapeCharacter}{textValue}{EscapeCharacter}", textValue);
|
||||
|
||||
// Get the bytes to write to the stream and write them
|
||||
output = _encoding.GetBytes(textValue);
|
||||
_outputStream.Write(output, 0, output.Length);
|
||||
|
||||
// only write a separator if we are moving in between values.
|
||||
// the last value should not be written.
|
||||
if (i < length - 1)
|
||||
_outputStream.Write(separatorBytes, 0, separatorBytes.Length);
|
||||
}
|
||||
|
||||
// output the newline sequence
|
||||
_outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
|
||||
_mCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Object Method
|
||||
|
||||
/// <summary>
|
||||
/// Writes a row of CSV text. It handles the special cases where the object is
|
||||
/// a dynamic object or and array. It also handles non-collection objects fine.
|
||||
/// If you do not like the way the output is handled, you can simply write an extension
|
||||
/// method of this class and use the WriteLine method instead.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <exception cref="System.ArgumentNullException">item.</exception>
|
||||
public void WriteObject(object item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case IDictionary typedItem:
|
||||
WriteLine(GetFilteredDictionary(typedItem));
|
||||
return;
|
||||
case ICollection typedItem:
|
||||
WriteLine(typedItem.Cast<object>());
|
||||
return;
|
||||
default:
|
||||
WriteLine(GetFilteredTypeProperties(item.GetType())
|
||||
.Select(x => x.ToFormattedString(item)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a row of CSV text. It handles the special cases where the object is
|
||||
/// a dynamic object or and array. It also handles non-collection objects fine.
|
||||
/// If you do not like the way the output is handled, you can simply write an extension
|
||||
/// method of this class and use the WriteLine method instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to write.</typeparam>
|
||||
/// <param name="item">The item.</param>
|
||||
public void WriteObject<T>(T item) => WriteObject(item as object);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a set of items, one per line and atomically by repeatedly calling the
|
||||
/// WriteObject method. For more info check out the description of the WriteObject
|
||||
/// method.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to write.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteObjects<T>(IEnumerable<T> items)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
foreach (var item in items)
|
||||
WriteObject(item);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Headings Methods
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of object to extract headings.</param>
|
||||
/// <exception cref="System.ArgumentNullException">type.</exception>
|
||||
public void WriteHeadings(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var properties = GetFilteredTypeProperties(type).Select(p => p.Name).Cast<object>();
|
||||
WriteLine(properties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to extract headings.</typeparam>
|
||||
public void WriteHeadings<T>() => WriteHeadings(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to extract headings.</param>
|
||||
/// <exception cref="System.ArgumentNullException">dictionary.</exception>
|
||||
public void WriteHeadings(IDictionary dictionary)
|
||||
{
|
||||
if (dictionary == null)
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
|
||||
WriteLine(GetFilteredDictionary(dictionary, true));
|
||||
}
|
||||
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(Stream outputStream, Boolean leaveOpen, Encoding encoding) {
|
||||
this._outputStream = outputStream;
|
||||
this._encoding = encoding;
|
||||
this._leaveStreamOpen = leaveOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It automatically closes the stream when disposing this writer.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(Stream outputStream, Encoding encoding)
|
||||
: this(outputStream, false, encoding) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It uses the Windows 1252 encoding and automatically closes
|
||||
/// the stream upon disposing this writer.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
public CsvWriter(Stream outputStream)
|
||||
: this(outputStream, false, Definitions.Windows1252Encoding) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It opens the file given file, automatically closes the stream upon
|
||||
/// disposing of this writer, and uses the Windows 1252 encoding.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
public CsvWriter(String filename)
|
||||
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It opens the file given file, automatically closes the stream upon
|
||||
/// disposing of this writer, and uses the given text encoding for output.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(String filename, Encoding encoding)
|
||||
: this(File.OpenWrite(filename), false, encoding) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the field separator character.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The separator character.
|
||||
/// </value>
|
||||
public Char SeparatorCharacter { get; set; } = ',';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the escape character to use to escape field values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The escape character.
|
||||
/// </value>
|
||||
public Char EscapeCharacter { get; set; } = '"';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the new line character sequence to use when writing a line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The new line sequence.
|
||||
/// </value>
|
||||
public String NewLineSequence { get; set; } = Environment.NewLine;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a list of properties to ignore when outputting CSV lines.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ignore property names.
|
||||
/// </value>
|
||||
public List<String> IgnorePropertyNames { get; } = new List<String>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of lines that have been written, including the headings line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public UInt64 Count {
|
||||
get {
|
||||
lock(this._syncLock) {
|
||||
return this._mCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items to a stream.
|
||||
/// It uses the Windows 1252 text encoding for output.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
|
||||
/// <returns>Number of item saved.</returns>
|
||||
public static Int32 SaveRecords<T>(IEnumerable<T> items, Stream stream, Boolean truncateData = false) {
|
||||
// truncate the file if it had data
|
||||
if(truncateData && stream.Length > 0) {
|
||||
stream.SetLength(0);
|
||||
}
|
||||
|
||||
using(CsvWriter writer = new CsvWriter(stream)) {
|
||||
writer.WriteHeadings<T>();
|
||||
writer.WriteObjects(items);
|
||||
return (Int32)writer.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items to a CSV file.
|
||||
/// If the file exits, it overwrites it. If it does not, it creates it.
|
||||
/// It uses the Windows 1252 text encoding for output.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="filePath">The file path.</param>
|
||||
/// <returns>Number of item saved.</returns>
|
||||
public static Int32 SaveRecords<T>(IEnumerable<T> items, String filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generic, main Write Line Method
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text. Items are converted to strings.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// If items are not string, the ToStringInvariant() method is called on them.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(params Object[] items)
|
||||
=> this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text. Items are converted to strings.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// If items are not string, the ToStringInvariant() method is called on them.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(IEnumerable<Object> items)
|
||||
=> this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(params String[] items) => this.WriteLine((IEnumerable<String>)items);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(IEnumerable<String> items) {
|
||||
lock(this._syncLock) {
|
||||
Int32 length = items.Count();
|
||||
Byte[] separatorBytes = this._encoding.GetBytes(new[] { this.SeparatorCharacter });
|
||||
Byte[] endOfLineBytes = this._encoding.GetBytes(this.NewLineSequence);
|
||||
|
||||
// Declare state variables here to avoid recreation, allocation and
|
||||
// reassignment in every loop
|
||||
Boolean needsEnclosing;
|
||||
String textValue;
|
||||
Byte[] output;
|
||||
|
||||
for(Int32 i = 0; i < length; i++) {
|
||||
textValue = items.ElementAt(i);
|
||||
|
||||
// Determine if we need the string to be enclosed
|
||||
// (it either contains an escape, new line, or separator char)
|
||||
needsEnclosing = textValue.IndexOf(this.SeparatorCharacter) >= 0
|
||||
|| textValue.IndexOf(this.EscapeCharacter) >= 0
|
||||
|| textValue.IndexOf('\r') >= 0
|
||||
|| textValue.IndexOf('\n') >= 0;
|
||||
|
||||
// Escape the escape characters by repeating them twice for every instance
|
||||
textValue = textValue.Replace($"{this.EscapeCharacter}",
|
||||
$"{this.EscapeCharacter}{this.EscapeCharacter}");
|
||||
|
||||
// Enclose the text value if we need to
|
||||
if(needsEnclosing) {
|
||||
textValue = String.Format($"{this.EscapeCharacter}{textValue}{this.EscapeCharacter}", textValue);
|
||||
}
|
||||
|
||||
// Get the bytes to write to the stream and write them
|
||||
output = this._encoding.GetBytes(textValue);
|
||||
this._outputStream.Write(output, 0, output.Length);
|
||||
|
||||
// only write a separator if we are moving in between values.
|
||||
// the last value should not be written.
|
||||
if(i < length - 1) {
|
||||
this._outputStream.Write(separatorBytes, 0, separatorBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// output the newline sequence
|
||||
this._outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
|
||||
this._mCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Object Method
|
||||
|
||||
/// <summary>
|
||||
/// Writes a row of CSV text. It handles the special cases where the object is
|
||||
/// a dynamic object or and array. It also handles non-collection objects fine.
|
||||
/// If you do not like the way the output is handled, you can simply write an extension
|
||||
/// method of this class and use the WriteLine method instead.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <exception cref="System.ArgumentNullException">item.</exception>
|
||||
public void WriteObject(Object item) {
|
||||
if(item == null) {
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
lock(this._syncLock) {
|
||||
switch(item) {
|
||||
case IDictionary typedItem:
|
||||
this.WriteLine(this.GetFilteredDictionary(typedItem));
|
||||
return;
|
||||
case ICollection typedItem:
|
||||
this.WriteLine(typedItem.Cast<Object>());
|
||||
return;
|
||||
default:
|
||||
this.WriteLine(this.GetFilteredTypeProperties(item.GetType())
|
||||
.Select(x => x.ToFormattedString(item)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a row of CSV text. It handles the special cases where the object is
|
||||
/// a dynamic object or and array. It also handles non-collection objects fine.
|
||||
/// If you do not like the way the output is handled, you can simply write an extension
|
||||
/// method of this class and use the WriteLine method instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to write.</typeparam>
|
||||
/// <param name="item">The item.</param>
|
||||
public void WriteObject<T>(T item) => this.WriteObject(item as Object);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a set of items, one per line and atomically by repeatedly calling the
|
||||
/// WriteObject method. For more info check out the description of the WriteObject
|
||||
/// method.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to write.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteObjects<T>(IEnumerable<T> items) {
|
||||
lock(this._syncLock) {
|
||||
foreach(T item in items) {
|
||||
this.WriteObject(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Headings Methods
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of object to extract headings.</param>
|
||||
/// <exception cref="System.ArgumentNullException">type.</exception>
|
||||
public void WriteHeadings(Type type) {
|
||||
if(type == null) {
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
IEnumerable<Object> properties = this.GetFilteredTypeProperties(type).Select(p => p.Name).Cast<Object>();
|
||||
this.WriteLine(properties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to extract headings.</typeparam>
|
||||
public void WriteHeadings<T>() => this.WriteHeadings(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to extract headings.</param>
|
||||
/// <exception cref="System.ArgumentNullException">dictionary.</exception>
|
||||
public void WriteHeadings(IDictionary dictionary) {
|
||||
if(dictionary == null) {
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
}
|
||||
|
||||
this.WriteLine(this.GetFilteredDictionary(dictionary, true));
|
||||
}
|
||||
|
||||
#if NET452
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to extract headings.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
/// <exception cref="ArgumentException">Unable to cast dynamic object to a suitable dictionary - item</exception>
|
||||
public void WriteHeadings(dynamic item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (!(item is IDictionary<string, object> dictionary))
|
||||
throw new ArgumentException("Unable to cast dynamic object to a suitable dictionary", nameof(item));
|
||||
|
||||
WriteHeadings(dictionary);
|
||||
}
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to extract headings.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
/// <exception cref="ArgumentException">Unable to cast dynamic object to a suitable dictionary - item</exception>
|
||||
public void WriteHeadings(dynamic item) {
|
||||
if(item == null) {
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if(!(item is IDictionary<global::System.String, global::System.Object> dictionary)) {
|
||||
throw new ArgumentException("Unable to cast dynamic object to a suitable dictionary", nameof(item));
|
||||
}
|
||||
|
||||
this.WriteHeadings(dictionary);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
@@ -424,55 +410,54 @@
|
||||
WriteHeadings(obj.GetType());
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposeAlsoManaged)
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
if (disposeAlsoManaged)
|
||||
{
|
||||
if (_leaveStreamOpen == false)
|
||||
{
|
||||
_outputStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_isDisposing = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Support Methods
|
||||
|
||||
private IEnumerable<string> GetFilteredDictionary(IDictionary dictionary, bool filterKeys = false)
|
||||
=> dictionary
|
||||
.Keys
|
||||
.Cast<object>()
|
||||
.Select(key => key == null ? string.Empty : key.ToStringInvariant())
|
||||
.Where(stringKey => !IgnorePropertyNames.Contains(stringKey))
|
||||
.Select(stringKey =>
|
||||
filterKeys
|
||||
? stringKey
|
||||
: dictionary[stringKey] == null ? string.Empty : dictionary[stringKey].ToStringInvariant());
|
||||
|
||||
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type)
|
||||
=> TypeCache.Retrieve(type, t =>
|
||||
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead))
|
||||
.Where(p => !IgnorePropertyNames.Contains(p.Name));
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => this.Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(Boolean disposeAlsoManaged) {
|
||||
if(this._isDisposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(disposeAlsoManaged) {
|
||||
if(this._leaveStreamOpen == false) {
|
||||
this._outputStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
this._isDisposing = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Support Methods
|
||||
|
||||
private IEnumerable<String> GetFilteredDictionary(IDictionary dictionary, Boolean filterKeys = false)
|
||||
=> dictionary
|
||||
.Keys
|
||||
.Cast<Object>()
|
||||
.Select(key => key == null ? String.Empty : key.ToStringInvariant())
|
||||
.Where(stringKey => !this.IgnorePropertyNames.Contains(stringKey))
|
||||
.Select(stringKey =>
|
||||
filterKeys
|
||||
? stringKey
|
||||
: dictionary[stringKey] == null ? String.Empty : dictionary[stringKey].ToStringInvariant());
|
||||
|
||||
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type)
|
||||
=> TypeCache.Retrieve(type, t =>
|
||||
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead))
|
||||
.Where(p => !this.IgnorePropertyNames.Contains(p.Name));
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,150 +1,134 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
internal class HumanizeJson
|
||||
{
|
||||
private readonly StringBuilder _builder = new StringBuilder();
|
||||
private readonly int _indent;
|
||||
private readonly string _indentStr;
|
||||
private readonly object _obj;
|
||||
|
||||
public HumanizeJson(object obj, int indent)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_indent = indent;
|
||||
_indentStr = new string(' ', indent * 4);
|
||||
_obj = obj;
|
||||
|
||||
ParseObject();
|
||||
}
|
||||
|
||||
public string GetResult() => _builder == null ? string.Empty : _builder.ToString().TrimEnd();
|
||||
|
||||
private void ParseObject()
|
||||
{
|
||||
switch (_obj)
|
||||
{
|
||||
case Dictionary<string, object> dictionary:
|
||||
AppendDictionary(dictionary);
|
||||
break;
|
||||
case List<object> list:
|
||||
AppendList(list);
|
||||
break;
|
||||
default:
|
||||
AppendString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendDictionary(Dictionary<string, object> objects)
|
||||
{
|
||||
foreach (var kvp in objects)
|
||||
{
|
||||
if (kvp.Value == null) continue;
|
||||
|
||||
var writeOutput = false;
|
||||
|
||||
switch (kvp.Value)
|
||||
{
|
||||
case Dictionary<string, object> valueDictionary:
|
||||
if (valueDictionary.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}{kvp.Key,-16}: object")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
case List<object> valueList:
|
||||
if (valueList.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}{kvp.Key,-16}: array[{valueList.Count}]")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
writeOutput = true;
|
||||
_builder.Append($"{_indentStr}{kvp.Key,-16}: ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (writeOutput)
|
||||
_builder.AppendLine(new HumanizeJson(kvp.Value, _indent + 1).GetResult());
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendList(List<object> objects)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var value in objects)
|
||||
{
|
||||
var writeOutput = false;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case Dictionary<string, object> valueDictionary:
|
||||
if (valueDictionary.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}[{index}]: object")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
case List<object> valueList:
|
||||
if (valueList.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}[{index}]: array[{valueList.Count}]")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
writeOutput = true;
|
||||
_builder.Append($"{_indentStr}[{index}]: ");
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if (writeOutput)
|
||||
_builder.AppendLine(new HumanizeJson(value, _indent + 1).GetResult());
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendString()
|
||||
{
|
||||
var stringValue = _obj.ToString();
|
||||
|
||||
if (stringValue.Length + _indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
|
||||
stringValue.IndexOf('\n') >= 0)
|
||||
{
|
||||
_builder.AppendLine();
|
||||
var stringLines = stringValue.ToLines().Select(l => l.Trim());
|
||||
|
||||
foreach (var line in stringLines)
|
||||
{
|
||||
_builder.AppendLine($"{_indentStr}{line}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_builder.Append($"{stringValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
internal class HumanizeJson {
|
||||
private readonly StringBuilder _builder = new StringBuilder();
|
||||
private readonly Int32 _indent;
|
||||
private readonly String _indentStr;
|
||||
private readonly Object _obj;
|
||||
|
||||
public HumanizeJson(Object obj, Int32 indent) {
|
||||
if(obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._indent = indent;
|
||||
this._indentStr = new String(' ', indent * 4);
|
||||
this._obj = obj;
|
||||
|
||||
this.ParseObject();
|
||||
}
|
||||
|
||||
public String GetResult() => this._builder == null ? String.Empty : this._builder.ToString().TrimEnd();
|
||||
|
||||
private void ParseObject() {
|
||||
switch(this._obj) {
|
||||
case Dictionary<String, Object> dictionary:
|
||||
this.AppendDictionary(dictionary);
|
||||
break;
|
||||
case List<Object> list:
|
||||
this.AppendList(list);
|
||||
break;
|
||||
default:
|
||||
this.AppendString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendDictionary(Dictionary<String, Object> objects) {
|
||||
foreach(KeyValuePair<String, Object> kvp in objects) {
|
||||
if(kvp.Value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Boolean writeOutput = false;
|
||||
|
||||
switch(kvp.Value) {
|
||||
case Dictionary<String, Object> valueDictionary:
|
||||
if(valueDictionary.Count > 0) {
|
||||
writeOutput = true;
|
||||
_ = this._builder
|
||||
.Append($"{this._indentStr}{kvp.Key,-16}: object")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
case List<Object> valueList:
|
||||
if(valueList.Count > 0) {
|
||||
writeOutput = true;
|
||||
_ = this._builder
|
||||
.Append($"{this._indentStr}{kvp.Key,-16}: array[{valueList.Count}]")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
writeOutput = true;
|
||||
_ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: ");
|
||||
break;
|
||||
}
|
||||
|
||||
if(writeOutput) {
|
||||
_ = this._builder.AppendLine(new HumanizeJson(kvp.Value, this._indent + 1).GetResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendList(List<Object> objects) {
|
||||
Int32 index = 0;
|
||||
foreach(Object value in objects) {
|
||||
Boolean writeOutput = false;
|
||||
|
||||
switch(value) {
|
||||
case Dictionary<String, Object> valueDictionary:
|
||||
if(valueDictionary.Count > 0) {
|
||||
writeOutput = true;
|
||||
_ = this._builder
|
||||
.Append($"{this._indentStr}[{index}]: object")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
case List<Object> valueList:
|
||||
if(valueList.Count > 0) {
|
||||
writeOutput = true;
|
||||
_ = this._builder
|
||||
.Append($"{this._indentStr}[{index}]: array[{valueList.Count}]")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
writeOutput = true;
|
||||
_ = this._builder.Append($"{this._indentStr}[{index}]: ");
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if(writeOutput) {
|
||||
_ = this._builder.AppendLine(new HumanizeJson(value, this._indent + 1).GetResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendString() {
|
||||
String stringValue = this._obj.ToString();
|
||||
|
||||
if(stringValue.Length + this._indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
|
||||
stringValue.IndexOf('\n') >= 0) {
|
||||
_ = this._builder.AppendLine();
|
||||
IEnumerable<String> stringLines = stringValue.ToLines().Select(l => l.Trim());
|
||||
|
||||
foreach(String line in stringLines) {
|
||||
_ = this._builder.AppendLine($"{this._indentStr}{line}");
|
||||
}
|
||||
} else {
|
||||
_ = this._builder.Append($"{stringValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,335 +1,302 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public static partial class Json
|
||||
{
|
||||
private class Converter
|
||||
{
|
||||
private static readonly ConcurrentDictionary<MemberInfo, string> MemberInfoNameCache =
|
||||
new ConcurrentDictionary<MemberInfo, string>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
|
||||
|
||||
private readonly object _target;
|
||||
private readonly Type _targetType;
|
||||
private readonly bool _includeNonPublic;
|
||||
|
||||
private Converter(
|
||||
object source,
|
||||
Type targetType,
|
||||
ref object targetInstance,
|
||||
bool includeNonPublic)
|
||||
{
|
||||
_targetType = targetInstance != null ? targetInstance.GetType() : targetType;
|
||||
_includeNonPublic = includeNonPublic;
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceType = source.GetType();
|
||||
|
||||
if (_targetType == null || _targetType == typeof(object)) _targetType = sourceType;
|
||||
if (sourceType == _targetType)
|
||||
{
|
||||
_target = source;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TrySetInstance(targetInstance, source, ref _target))
|
||||
return;
|
||||
|
||||
ResolveObject(source, ref _target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
|
||||
/// <returns>The target object.</returns>
|
||||
internal static object FromJsonResult(object source,
|
||||
Type targetType,
|
||||
bool includeNonPublic)
|
||||
{
|
||||
object nullRef = null;
|
||||
return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult();
|
||||
}
|
||||
|
||||
private static object FromJsonResult(object source,
|
||||
Type targetType,
|
||||
ref object targetInstance,
|
||||
bool includeNonPublic)
|
||||
{
|
||||
return new Converter(source, targetType, ref targetInstance, includeNonPublic).GetResult();
|
||||
}
|
||||
|
||||
private static Type GetAddMethodParameterType(Type targetType)
|
||||
=> ListAddMethodCache.GetOrAdd(targetType,
|
||||
x => x.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 1)?
|
||||
.GetParameters()[0]
|
||||
.ParameterType);
|
||||
|
||||
private static void GetByteArray(string sourceString, ref object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
target = Convert.FromBase64String(sourceString);
|
||||
} // Try conversion from Base 64
|
||||
catch
|
||||
{
|
||||
target = Encoding.UTF8.GetBytes(sourceString);
|
||||
} // Get the string bytes in UTF8
|
||||
}
|
||||
|
||||
private static object GetSourcePropertyValue(IDictionary<string, object> sourceProperties,
|
||||
MemberInfo targetProperty)
|
||||
{
|
||||
var targetPropertyName = MemberInfoNameCache.GetOrAdd(
|
||||
targetProperty,
|
||||
x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name);
|
||||
|
||||
return sourceProperties.GetValueOrDefault(targetPropertyName);
|
||||
}
|
||||
|
||||
private bool TrySetInstance(object targetInstance, object source, ref object target)
|
||||
{
|
||||
if (targetInstance == null)
|
||||
{
|
||||
// Try to create a default instance
|
||||
try
|
||||
{
|
||||
source.CreateTarget(_targetType, _includeNonPublic, ref target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target = targetInstance;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private object GetResult() => _target ?? _targetType.GetDefault();
|
||||
|
||||
private void ResolveObject(object source, ref object target)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
// Case 0: Special Cases Handling (Source and Target are of specific convertible types)
|
||||
// Case 0.1: Source is string, Target is byte[]
|
||||
case string sourceString when _targetType == typeof(byte[]):
|
||||
GetByteArray(sourceString, ref target);
|
||||
break;
|
||||
|
||||
// Case 1.1: Source is Dictionary, Target is IDictionary
|
||||
case Dictionary<string, object> sourceProperties when target is IDictionary targetDictionary:
|
||||
PopulateDictionary(sourceProperties, targetDictionary);
|
||||
break;
|
||||
|
||||
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
|
||||
case Dictionary<string, object> sourceProperties:
|
||||
PopulateObject(sourceProperties);
|
||||
break;
|
||||
|
||||
// Case 2.1: Source is List, Target is Array
|
||||
case List<object> sourceList when target is Array targetArray:
|
||||
PopulateArray(sourceList, targetArray);
|
||||
break;
|
||||
|
||||
// Case 2.2: Source is List, Target is IList
|
||||
case List<object> sourceList when target is IList targetList:
|
||||
PopulateIList(sourceList, targetList);
|
||||
break;
|
||||
|
||||
// Case 3: Source is a simple type; Attempt conversion
|
||||
default:
|
||||
var sourceStringValue = source.ToStringInvariant();
|
||||
|
||||
// Handle basic types or enumerations if not
|
||||
if (!_targetType.TryParseBasicType(sourceStringValue, out target))
|
||||
GetEnumValue(sourceStringValue, ref target);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateIList(IList<object> objects, IList list)
|
||||
{
|
||||
var parameterType = GetAddMethodParameterType(_targetType);
|
||||
if (parameterType == null) return;
|
||||
|
||||
foreach (var item in objects)
|
||||
{
|
||||
try
|
||||
{
|
||||
list.Add(FromJsonResult(
|
||||
item,
|
||||
parameterType,
|
||||
_includeNonPublic));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateArray(IList<object> objects, Array array)
|
||||
{
|
||||
var elementType = _targetType.GetElementType();
|
||||
|
||||
for (var i = 0; i < objects.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetItem = FromJsonResult(
|
||||
objects[i],
|
||||
elementType,
|
||||
_includeNonPublic);
|
||||
array.SetValue(targetItem, i);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetEnumValue(string sourceStringValue, ref object target)
|
||||
{
|
||||
var enumType = Nullable.GetUnderlyingType(_targetType);
|
||||
if (enumType == null && _targetType.GetTypeInfo().IsEnum) enumType = _targetType;
|
||||
if (enumType == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
target = Enum.Parse(enumType, sourceStringValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateDictionary(IDictionary<string, object> sourceProperties, IDictionary targetDictionary)
|
||||
{
|
||||
// find the add method of the target dictionary
|
||||
var addMethod = _targetType.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 2);
|
||||
|
||||
// skip if we don't have a compatible add method
|
||||
if (addMethod == null) return;
|
||||
var addMethodParameters = addMethod.GetParameters();
|
||||
if (addMethodParameters[0].ParameterType != typeof(string)) return;
|
||||
|
||||
// Retrieve the target entry type
|
||||
var targetEntryType = addMethodParameters[1].ParameterType;
|
||||
|
||||
// Add the items to the target dictionary
|
||||
foreach (var sourceProperty in sourceProperties)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetEntryValue = FromJsonResult(
|
||||
sourceProperty.Value,
|
||||
targetEntryType,
|
||||
_includeNonPublic);
|
||||
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateObject(IDictionary<string, object> sourceProperties)
|
||||
{
|
||||
if (_targetType.IsValueType())
|
||||
{
|
||||
PopulateFields(sourceProperties);
|
||||
}
|
||||
|
||||
PopulateProperties(sourceProperties);
|
||||
}
|
||||
|
||||
private void PopulateProperties(IDictionary<string, object> sourceProperties)
|
||||
{
|
||||
var properties = PropertyTypeCache.RetrieveFilteredProperties(_targetType, false, p => p.CanWrite);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
|
||||
if (sourcePropertyValue == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var currentPropertyValue = !property.PropertyType.IsArray
|
||||
? property.GetCacheGetMethod(_includeNonPublic)(_target)
|
||||
: null;
|
||||
|
||||
var targetPropertyValue = FromJsonResult(
|
||||
sourcePropertyValue,
|
||||
property.PropertyType,
|
||||
ref currentPropertyValue,
|
||||
_includeNonPublic);
|
||||
|
||||
property.GetCacheSetMethod(_includeNonPublic)(_target, new[] { targetPropertyValue });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateFields(IDictionary<string, object> sourceProperties)
|
||||
{
|
||||
foreach (var field in FieldTypeCache.RetrieveAllFields(_targetType))
|
||||
{
|
||||
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
|
||||
if (sourcePropertyValue == null) continue;
|
||||
|
||||
var targetPropertyValue = FromJsonResult(
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Unosquare.Swan.Attributes;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public static partial class Json {
|
||||
private class Converter {
|
||||
private static readonly ConcurrentDictionary<MemberInfo, String> MemberInfoNameCache =
|
||||
new ConcurrentDictionary<MemberInfo, String>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
|
||||
|
||||
private readonly Object _target;
|
||||
private readonly Type _targetType;
|
||||
private readonly Boolean _includeNonPublic;
|
||||
|
||||
private Converter(
|
||||
Object source,
|
||||
Type targetType,
|
||||
ref Object targetInstance,
|
||||
Boolean includeNonPublic) {
|
||||
this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
|
||||
this._includeNonPublic = includeNonPublic;
|
||||
|
||||
if(source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Type sourceType = source.GetType();
|
||||
|
||||
if(this._targetType == null || this._targetType == typeof(Object)) {
|
||||
this._targetType = sourceType;
|
||||
}
|
||||
|
||||
if(sourceType == this._targetType) {
|
||||
this._target = source;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ResolveObject(source, ref this._target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
|
||||
/// <returns>The target object.</returns>
|
||||
internal static Object FromJsonResult(Object source,
|
||||
Type targetType,
|
||||
Boolean includeNonPublic) {
|
||||
Object nullRef = null;
|
||||
return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult();
|
||||
}
|
||||
|
||||
private static Object FromJsonResult(Object source,
|
||||
Type targetType,
|
||||
ref Object targetInstance,
|
||||
Boolean includeNonPublic) => new Converter(source, targetType, ref targetInstance, includeNonPublic).GetResult();
|
||||
|
||||
private static Type GetAddMethodParameterType(Type targetType)
|
||||
=> ListAddMethodCache.GetOrAdd(targetType,
|
||||
x => x.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 1)?
|
||||
.GetParameters()[0]
|
||||
.ParameterType);
|
||||
|
||||
private static void GetByteArray(String sourceString, ref Object target) {
|
||||
try {
|
||||
target = Convert.FromBase64String(sourceString);
|
||||
} // Try conversion from Base 64
|
||||
catch {
|
||||
target = Encoding.UTF8.GetBytes(sourceString);
|
||||
} // Get the string bytes in UTF8
|
||||
}
|
||||
|
||||
private static Object GetSourcePropertyValue(IDictionary<String, Object> sourceProperties,
|
||||
MemberInfo targetProperty) {
|
||||
String targetPropertyName = MemberInfoNameCache.GetOrAdd(
|
||||
targetProperty,
|
||||
x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name);
|
||||
|
||||
return sourceProperties.GetValueOrDefault(targetPropertyName);
|
||||
}
|
||||
|
||||
private Boolean TrySetInstance(Object targetInstance, Object source, ref Object target) {
|
||||
if(targetInstance == null) {
|
||||
// Try to create a default instance
|
||||
try {
|
||||
source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
target = targetInstance;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Object GetResult() => this._target ?? this._targetType.GetDefault();
|
||||
|
||||
private void ResolveObject(Object source, ref Object target) {
|
||||
switch(source) {
|
||||
// Case 0: Special Cases Handling (Source and Target are of specific convertible types)
|
||||
// Case 0.1: Source is string, Target is byte[]
|
||||
case String sourceString when this._targetType == typeof(Byte[]):
|
||||
GetByteArray(sourceString, ref target);
|
||||
break;
|
||||
|
||||
// Case 1.1: Source is Dictionary, Target is IDictionary
|
||||
case Dictionary<String, Object> sourceProperties when target is IDictionary targetDictionary:
|
||||
this.PopulateDictionary(sourceProperties, targetDictionary);
|
||||
break;
|
||||
|
||||
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
|
||||
case Dictionary<String, Object> sourceProperties:
|
||||
this.PopulateObject(sourceProperties);
|
||||
break;
|
||||
|
||||
// Case 2.1: Source is List, Target is Array
|
||||
case List<Object> sourceList when target is Array targetArray:
|
||||
this.PopulateArray(sourceList, targetArray);
|
||||
break;
|
||||
|
||||
// Case 2.2: Source is List, Target is IList
|
||||
case List<Object> sourceList when target is IList targetList:
|
||||
this.PopulateIList(sourceList, targetList);
|
||||
break;
|
||||
|
||||
// Case 3: Source is a simple type; Attempt conversion
|
||||
default:
|
||||
String sourceStringValue = source.ToStringInvariant();
|
||||
|
||||
// Handle basic types or enumerations if not
|
||||
if(!this._targetType.TryParseBasicType(sourceStringValue, out target)) {
|
||||
this.GetEnumValue(sourceStringValue, ref target);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateIList(IList<Object> objects, IList list) {
|
||||
Type parameterType = GetAddMethodParameterType(this._targetType);
|
||||
if(parameterType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(Object item in objects) {
|
||||
try {
|
||||
_ = list.Add(FromJsonResult(
|
||||
item,
|
||||
parameterType,
|
||||
this._includeNonPublic));
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateArray(IList<Object> objects, Array array) {
|
||||
Type elementType = this._targetType.GetElementType();
|
||||
|
||||
for(Int32 i = 0; i < objects.Count; i++) {
|
||||
try {
|
||||
Object targetItem = FromJsonResult(
|
||||
objects[i],
|
||||
elementType,
|
||||
this._includeNonPublic);
|
||||
array.SetValue(targetItem, i);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetEnumValue(String sourceStringValue, ref Object target) {
|
||||
Type enumType = Nullable.GetUnderlyingType(this._targetType);
|
||||
if(enumType == null && this._targetType.GetTypeInfo().IsEnum) {
|
||||
enumType = this._targetType;
|
||||
}
|
||||
|
||||
if(enumType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
target = Enum.Parse(enumType, sourceStringValue);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateDictionary(IDictionary<String, Object> sourceProperties, IDictionary targetDictionary) {
|
||||
// find the add method of the target dictionary
|
||||
MethodInfo addMethod = this._targetType.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 2);
|
||||
|
||||
// skip if we don't have a compatible add method
|
||||
if(addMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ParameterInfo[] addMethodParameters = addMethod.GetParameters();
|
||||
if(addMethodParameters[0].ParameterType != typeof(String)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the target entry type
|
||||
Type targetEntryType = addMethodParameters[1].ParameterType;
|
||||
|
||||
// Add the items to the target dictionary
|
||||
foreach(KeyValuePair<String, Object> sourceProperty in sourceProperties) {
|
||||
try {
|
||||
Object targetEntryValue = FromJsonResult(
|
||||
sourceProperty.Value,
|
||||
targetEntryType,
|
||||
this._includeNonPublic);
|
||||
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateObject(IDictionary<String, Object> sourceProperties) {
|
||||
if(this._targetType.IsValueType()) {
|
||||
this.PopulateFields(sourceProperties);
|
||||
}
|
||||
|
||||
this.PopulateProperties(sourceProperties);
|
||||
}
|
||||
|
||||
private void PopulateProperties(IDictionary<String, Object> sourceProperties) {
|
||||
IEnumerable<PropertyInfo> properties = PropertyTypeCache.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite);
|
||||
|
||||
foreach(PropertyInfo property in properties) {
|
||||
Object sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
|
||||
if(sourcePropertyValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Object currentPropertyValue = !property.PropertyType.IsArray
|
||||
? property.GetCacheGetMethod(this._includeNonPublic)(this._target)
|
||||
: null;
|
||||
|
||||
Object targetPropertyValue = FromJsonResult(
|
||||
sourcePropertyValue,
|
||||
property.PropertyType,
|
||||
ref currentPropertyValue,
|
||||
this._includeNonPublic);
|
||||
|
||||
property.GetCacheSetMethod(this._includeNonPublic)(this._target, new[] { targetPropertyValue });
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateFields(IDictionary<String, Object> sourceProperties) {
|
||||
foreach(FieldInfo field in FieldTypeCache.RetrieveAllFields(this._targetType)) {
|
||||
Object sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
|
||||
if(sourcePropertyValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object targetPropertyValue = FromJsonResult(
|
||||
sourcePropertyValue,
|
||||
field.FieldType,
|
||||
_includeNonPublic);
|
||||
|
||||
try
|
||||
{
|
||||
field.SetValue(_target, targetPropertyValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._includeNonPublic);
|
||||
|
||||
try {
|
||||
field.SetValue(this._target, targetPropertyValue);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,374 +1,366 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public partial class Json {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// A simple JSON Deserializer.
|
||||
/// </summary>
|
||||
public partial class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple JSON Deserializer.
|
||||
/// </summary>
|
||||
private class Deserializer
|
||||
{
|
||||
#region State Variables
|
||||
|
||||
private readonly object _result;
|
||||
private readonly Dictionary<string, object> _resultObject;
|
||||
private readonly List<object> _resultArray;
|
||||
|
||||
private readonly ReadState _state = ReadState.WaitingForRootOpen;
|
||||
private readonly string _currentFieldName;
|
||||
private readonly string _json;
|
||||
|
||||
private int _index;
|
||||
|
||||
#endregion
|
||||
|
||||
private Deserializer(string json, int startIndex)
|
||||
{
|
||||
_json = json;
|
||||
|
||||
for (_index = startIndex; _index < _json.Length; _index++)
|
||||
{
|
||||
#region Wait for { or [
|
||||
|
||||
if (_state == ReadState.WaitingForRootOpen)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
if (_json[_index] == OpenObjectChar)
|
||||
{
|
||||
_resultObject = new Dictionary<string, object>();
|
||||
_state = ReadState.WaitingForField;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_json[_index] == OpenArrayChar)
|
||||
{
|
||||
_resultArray = new List<object>();
|
||||
_state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for opening field " (only applies for object results)
|
||||
|
||||
if (_state == ReadState.WaitingForField)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
// Handle empty arrays and empty objects
|
||||
if ((_resultObject != null && _json[_index] == CloseObjectChar)
|
||||
|| (_resultArray != null && _json[_index] == CloseArrayChar))
|
||||
{
|
||||
_result = _resultObject ?? _resultArray as object;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_json[_index] != StringQuotedChar)
|
||||
throw CreateParserException($"'{StringQuotedChar}'");
|
||||
|
||||
var charCount = GetFieldNameCount();
|
||||
|
||||
_currentFieldName = Unescape(_json.SliceLength(_index + 1, charCount));
|
||||
_index += charCount + 1;
|
||||
_state = ReadState.WaitingForColon;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for field-value separator : (only applies for object results
|
||||
|
||||
if (_state == ReadState.WaitingForColon)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
if (_json[_index] != ValueSeparatorChar)
|
||||
throw CreateParserException($"'{ValueSeparatorChar}'");
|
||||
|
||||
_state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for and Parse the value
|
||||
|
||||
if (_state == ReadState.WaitingForValue)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
// Handle empty arrays and empty objects
|
||||
if ((_resultObject != null && _json[_index] == CloseObjectChar)
|
||||
|| (_resultArray != null && _json[_index] == CloseArrayChar))
|
||||
{
|
||||
_result = _resultObject ?? _resultArray as object;
|
||||
return;
|
||||
}
|
||||
|
||||
// determine the value based on what it starts with
|
||||
switch (_json[_index])
|
||||
{
|
||||
case StringQuotedChar: // expect a string
|
||||
ExtractStringQuoted();
|
||||
break;
|
||||
|
||||
case OpenObjectChar: // expect object
|
||||
case OpenArrayChar: // expect array
|
||||
ExtractObject();
|
||||
break;
|
||||
|
||||
case 't': // expect true
|
||||
ExtractConstant(TrueLiteral, true);
|
||||
break;
|
||||
|
||||
case 'f': // expect false
|
||||
ExtractConstant(FalseLiteral, false);
|
||||
break;
|
||||
|
||||
case 'n': // expect null
|
||||
ExtractConstant(NullLiteral, null);
|
||||
break;
|
||||
|
||||
default: // expect number
|
||||
ExtractNumber();
|
||||
break;
|
||||
}
|
||||
|
||||
_currentFieldName = null;
|
||||
_state = ReadState.WaitingForNextOrRootClose;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for closing ], } or an additional field or value ,
|
||||
|
||||
if (_state != ReadState.WaitingForNextOrRootClose) continue;
|
||||
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
if (_json[_index] == FieldSeparatorChar)
|
||||
{
|
||||
if (_resultObject != null)
|
||||
{
|
||||
_state = ReadState.WaitingForField;
|
||||
_currentFieldName = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
_state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((_resultObject != null && _json[_index] == CloseObjectChar) ||
|
||||
(_resultArray != null && _json[_index] == CloseArrayChar))
|
||||
{
|
||||
_result = _resultObject ?? _resultArray as object;
|
||||
return;
|
||||
}
|
||||
|
||||
throw CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
internal static object DeserializeInternal(string json) => new Deserializer(json, 0)._result;
|
||||
|
||||
private static string Unescape(string str)
|
||||
{
|
||||
// check if we need to unescape at all
|
||||
if (str.IndexOf(StringEscapeChar) < 0)
|
||||
return str;
|
||||
|
||||
var builder = new StringBuilder(str.Length);
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (str[i] != StringEscapeChar)
|
||||
{
|
||||
builder.Append(str[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 > str.Length - 1)
|
||||
break;
|
||||
|
||||
// escape sequence begins here
|
||||
switch (str[i + 1])
|
||||
{
|
||||
case 'u':
|
||||
i = ExtractEscapeSequence(str, i, builder);
|
||||
break;
|
||||
case 'b':
|
||||
builder.Append('\b');
|
||||
i += 1;
|
||||
break;
|
||||
case 't':
|
||||
builder.Append('\t');
|
||||
i += 1;
|
||||
break;
|
||||
case 'n':
|
||||
builder.Append('\n');
|
||||
i += 1;
|
||||
break;
|
||||
case 'f':
|
||||
builder.Append('\f');
|
||||
i += 1;
|
||||
break;
|
||||
case 'r':
|
||||
builder.Append('\r');
|
||||
i += 1;
|
||||
break;
|
||||
default:
|
||||
builder.Append(str[i + 1]);
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static int ExtractEscapeSequence(string str, int i, StringBuilder builder)
|
||||
{
|
||||
var startIndex = i + 2;
|
||||
var endIndex = i + 5;
|
||||
if (endIndex > str.Length - 1)
|
||||
{
|
||||
builder.Append(str[i + 1]);
|
||||
i += 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
var hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes();
|
||||
builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode));
|
||||
i += 5;
|
||||
return i;
|
||||
}
|
||||
|
||||
private int GetFieldNameCount()
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = _index + 1; j < _json.Length; j++)
|
||||
{
|
||||
if (_json[j] == StringQuotedChar && _json[j - 1] != StringEscapeChar)
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
return charCount;
|
||||
}
|
||||
|
||||
private void ExtractObject()
|
||||
{
|
||||
// Extract and set the value
|
||||
var deserializer = new Deserializer(_json, _index);
|
||||
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = deserializer._result;
|
||||
else
|
||||
_resultArray.Add(deserializer._result);
|
||||
|
||||
_index = deserializer._index;
|
||||
}
|
||||
|
||||
private void ExtractNumber()
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = _index; j < _json.Length; j++)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json[j]) || _json[j] == FieldSeparatorChar
|
||||
|| (_resultObject != null && _json[j] == CloseObjectChar)
|
||||
|| (_resultArray != null && _json[j] == CloseArrayChar))
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var stringValue = _json.SliceLength(_index, charCount);
|
||||
|
||||
if (decimal.TryParse(stringValue, out var value) == false)
|
||||
throw CreateParserException("[number]");
|
||||
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = value;
|
||||
else
|
||||
_resultArray.Add(value);
|
||||
|
||||
_index += charCount - 1;
|
||||
}
|
||||
|
||||
private void ExtractConstant(string boolValue, bool? value)
|
||||
{
|
||||
if (!_json.SliceLength(_index, boolValue.Length).Equals(boolValue))
|
||||
throw CreateParserException($"'{ValueSeparatorChar}'");
|
||||
|
||||
// Extract and set the value
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = value;
|
||||
else
|
||||
_resultArray.Add(value);
|
||||
|
||||
_index += boolValue.Length - 1;
|
||||
}
|
||||
|
||||
private void ExtractStringQuoted()
|
||||
{
|
||||
var charCount = 0;
|
||||
var escapeCharFound = false;
|
||||
for (var j = _index + 1; j < _json.Length; j++)
|
||||
{
|
||||
if (_json[j] == StringQuotedChar && !escapeCharFound)
|
||||
break;
|
||||
|
||||
escapeCharFound = _json[j] == StringEscapeChar && !escapeCharFound;
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var value = Unescape(_json.SliceLength(_index + 1, charCount));
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = value;
|
||||
else
|
||||
_resultArray.Add(value);
|
||||
|
||||
_index += charCount + 1;
|
||||
}
|
||||
|
||||
private FormatException CreateParserException(string expected)
|
||||
{
|
||||
var textPosition = _json.TextPositionAt(_index);
|
||||
return new FormatException(
|
||||
$"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {_state}): Expected {expected} but got '{_json[_index]}'.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different JSON read states.
|
||||
/// </summary>
|
||||
private enum ReadState
|
||||
{
|
||||
WaitingForRootOpen,
|
||||
WaitingForField,
|
||||
WaitingForColon,
|
||||
WaitingForValue,
|
||||
WaitingForNextOrRootClose,
|
||||
}
|
||||
}
|
||||
}
|
||||
private class Deserializer {
|
||||
#region State Variables
|
||||
|
||||
private readonly Object _result;
|
||||
private readonly Dictionary<String, Object> _resultObject;
|
||||
private readonly List<Object> _resultArray;
|
||||
|
||||
private readonly ReadState _state = ReadState.WaitingForRootOpen;
|
||||
private readonly String _currentFieldName;
|
||||
private readonly String _json;
|
||||
|
||||
private Int32 _index;
|
||||
|
||||
#endregion
|
||||
|
||||
private Deserializer(String json, Int32 startIndex) {
|
||||
this._json = json;
|
||||
|
||||
for(this._index = startIndex; this._index < this._json.Length; this._index++) {
|
||||
#region Wait for { or [
|
||||
|
||||
if(this._state == ReadState.WaitingForRootOpen) {
|
||||
if(Char.IsWhiteSpace(this._json, this._index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this._json[this._index] == OpenObjectChar) {
|
||||
this._resultObject = new Dictionary<String, Object>();
|
||||
this._state = ReadState.WaitingForField;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this._json[this._index] == OpenArrayChar) {
|
||||
this._resultArray = new List<Object>();
|
||||
this._state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw this.CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for opening field " (only applies for object results)
|
||||
|
||||
if(this._state == ReadState.WaitingForField) {
|
||||
if(Char.IsWhiteSpace(this._json, this._index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle empty arrays and empty objects
|
||||
if(this._resultObject != null && this._json[this._index] == CloseObjectChar
|
||||
|| this._resultArray != null && this._json[this._index] == CloseArrayChar) {
|
||||
this._result = this._resultObject ?? this._resultArray as Object;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this._json[this._index] != StringQuotedChar) {
|
||||
throw this.CreateParserException($"'{StringQuotedChar}'");
|
||||
}
|
||||
|
||||
Int32 charCount = this.GetFieldNameCount();
|
||||
|
||||
this._currentFieldName = Unescape(this._json.SliceLength(this._index + 1, charCount));
|
||||
this._index += charCount + 1;
|
||||
this._state = ReadState.WaitingForColon;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for field-value separator : (only applies for object results
|
||||
|
||||
if(this._state == ReadState.WaitingForColon) {
|
||||
if(Char.IsWhiteSpace(this._json, this._index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this._json[this._index] != ValueSeparatorChar) {
|
||||
throw this.CreateParserException($"'{ValueSeparatorChar}'");
|
||||
}
|
||||
|
||||
this._state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for and Parse the value
|
||||
|
||||
if(this._state == ReadState.WaitingForValue) {
|
||||
if(Char.IsWhiteSpace(this._json, this._index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle empty arrays and empty objects
|
||||
if(this._resultObject != null && this._json[this._index] == CloseObjectChar
|
||||
|| this._resultArray != null && this._json[this._index] == CloseArrayChar) {
|
||||
this._result = this._resultObject ?? this._resultArray as Object;
|
||||
return;
|
||||
}
|
||||
|
||||
// determine the value based on what it starts with
|
||||
switch(this._json[this._index]) {
|
||||
case StringQuotedChar: // expect a string
|
||||
this.ExtractStringQuoted();
|
||||
break;
|
||||
|
||||
case OpenObjectChar: // expect object
|
||||
case OpenArrayChar: // expect array
|
||||
this.ExtractObject();
|
||||
break;
|
||||
|
||||
case 't': // expect true
|
||||
this.ExtractConstant(TrueLiteral, true);
|
||||
break;
|
||||
|
||||
case 'f': // expect false
|
||||
this.ExtractConstant(FalseLiteral, false);
|
||||
break;
|
||||
|
||||
case 'n': // expect null
|
||||
this.ExtractConstant(NullLiteral, null);
|
||||
break;
|
||||
|
||||
default: // expect number
|
||||
this.ExtractNumber();
|
||||
break;
|
||||
}
|
||||
|
||||
this._currentFieldName = null;
|
||||
this._state = ReadState.WaitingForNextOrRootClose;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for closing ], } or an additional field or value ,
|
||||
|
||||
if(this._state != ReadState.WaitingForNextOrRootClose) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Char.IsWhiteSpace(this._json, this._index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this._json[this._index] == FieldSeparatorChar) {
|
||||
if(this._resultObject != null) {
|
||||
this._state = ReadState.WaitingForField;
|
||||
this._currentFieldName = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
this._state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this._resultObject != null && this._json[this._index] == CloseObjectChar ||
|
||||
this._resultArray != null && this._json[this._index] == CloseArrayChar) {
|
||||
this._result = this._resultObject ?? this._resultArray as Object;
|
||||
return;
|
||||
}
|
||||
|
||||
throw this.CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
internal static Object DeserializeInternal(String json) => new Deserializer(json, 0)._result;
|
||||
|
||||
private static String Unescape(String str) {
|
||||
// check if we need to unescape at all
|
||||
if(str.IndexOf(StringEscapeChar) < 0) {
|
||||
return str;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder(str.Length);
|
||||
for(Int32 i = 0; i < str.Length; i++) {
|
||||
if(str[i] != StringEscapeChar) {
|
||||
_ = builder.Append(str[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(i + 1 > str.Length - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// escape sequence begins here
|
||||
switch(str[i + 1]) {
|
||||
case 'u':
|
||||
i = ExtractEscapeSequence(str, i, builder);
|
||||
break;
|
||||
case 'b':
|
||||
_ = builder.Append('\b');
|
||||
i += 1;
|
||||
break;
|
||||
case 't':
|
||||
_ = builder.Append('\t');
|
||||
i += 1;
|
||||
break;
|
||||
case 'n':
|
||||
_ = builder.Append('\n');
|
||||
i += 1;
|
||||
break;
|
||||
case 'f':
|
||||
_ = builder.Append('\f');
|
||||
i += 1;
|
||||
break;
|
||||
case 'r':
|
||||
_ = builder.Append('\r');
|
||||
i += 1;
|
||||
break;
|
||||
default:
|
||||
_ = builder.Append(str[i + 1]);
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static Int32 ExtractEscapeSequence(String str, Int32 i, StringBuilder builder) {
|
||||
Int32 startIndex = i + 2;
|
||||
Int32 endIndex = i + 5;
|
||||
if(endIndex > str.Length - 1) {
|
||||
_ = builder.Append(str[i + 1]);
|
||||
i += 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
Byte[] hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes();
|
||||
_ = builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode));
|
||||
i += 5;
|
||||
return i;
|
||||
}
|
||||
|
||||
private Int32 GetFieldNameCount() {
|
||||
Int32 charCount = 0;
|
||||
for(Int32 j = this._index + 1; j < this._json.Length; j++) {
|
||||
if(this._json[j] == StringQuotedChar && this._json[j - 1] != StringEscapeChar) {
|
||||
break;
|
||||
}
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
return charCount;
|
||||
}
|
||||
|
||||
private void ExtractObject() {
|
||||
// Extract and set the value
|
||||
Deserializer deserializer = new Deserializer(this._json, this._index);
|
||||
|
||||
if(this._currentFieldName != null) {
|
||||
this._resultObject[this._currentFieldName] = deserializer._result;
|
||||
} else {
|
||||
this._resultArray.Add(deserializer._result);
|
||||
}
|
||||
|
||||
this._index = deserializer._index;
|
||||
}
|
||||
|
||||
private void ExtractNumber() {
|
||||
Int32 charCount = 0;
|
||||
for(Int32 j = this._index; j < this._json.Length; j++) {
|
||||
if(Char.IsWhiteSpace(this._json[j]) || this._json[j] == FieldSeparatorChar
|
||||
|| this._resultObject != null && this._json[j] == CloseObjectChar
|
||||
|| this._resultArray != null && this._json[j] == CloseArrayChar) {
|
||||
break;
|
||||
}
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
String stringValue = this._json.SliceLength(this._index, charCount);
|
||||
|
||||
if(Decimal.TryParse(stringValue, out Decimal value) == false) {
|
||||
throw this.CreateParserException("[number]");
|
||||
}
|
||||
|
||||
if(this._currentFieldName != null) {
|
||||
this._resultObject[this._currentFieldName] = value;
|
||||
} else {
|
||||
this._resultArray.Add(value);
|
||||
}
|
||||
|
||||
this._index += charCount - 1;
|
||||
}
|
||||
|
||||
private void ExtractConstant(String boolValue, Boolean? value) {
|
||||
if(!this._json.SliceLength(this._index, boolValue.Length).Equals(boolValue)) {
|
||||
throw this.CreateParserException($"'{ValueSeparatorChar}'");
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
if(this._currentFieldName != null) {
|
||||
this._resultObject[this._currentFieldName] = value;
|
||||
} else {
|
||||
this._resultArray.Add(value);
|
||||
}
|
||||
|
||||
this._index += boolValue.Length - 1;
|
||||
}
|
||||
|
||||
private void ExtractStringQuoted() {
|
||||
Int32 charCount = 0;
|
||||
Boolean escapeCharFound = false;
|
||||
for(Int32 j = this._index + 1; j < this._json.Length; j++) {
|
||||
if(this._json[j] == StringQuotedChar && !escapeCharFound) {
|
||||
break;
|
||||
}
|
||||
|
||||
escapeCharFound = this._json[j] == StringEscapeChar && !escapeCharFound;
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
String value = Unescape(this._json.SliceLength(this._index + 1, charCount));
|
||||
if(this._currentFieldName != null) {
|
||||
this._resultObject[this._currentFieldName] = value;
|
||||
} else {
|
||||
this._resultArray.Add(value);
|
||||
}
|
||||
|
||||
this._index += charCount + 1;
|
||||
}
|
||||
|
||||
private FormatException CreateParserException(String expected) {
|
||||
Tuple<Int32, Int32> textPosition = this._json.TextPositionAt(this._index);
|
||||
return new FormatException(
|
||||
$"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {this._state}): Expected {expected} but got '{this._json[this._index]}'.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different JSON read states.
|
||||
/// </summary>
|
||||
private enum ReadState {
|
||||
WaitingForRootOpen,
|
||||
WaitingForField,
|
||||
WaitingForColon,
|
||||
WaitingForValue,
|
||||
WaitingForNextOrRootClose,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,359 +1,347 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public partial class Json {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// A simple JSON serializer.
|
||||
/// </summary>
|
||||
public partial class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple JSON serializer.
|
||||
/// </summary>
|
||||
private class Serializer
|
||||
{
|
||||
#region Private Declarations
|
||||
|
||||
private static readonly Dictionary<int, string> IndentStrings = new Dictionary<int, string>();
|
||||
|
||||
private readonly SerializerOptions _options;
|
||||
private readonly string _result;
|
||||
private readonly StringBuilder _builder;
|
||||
private readonly string _lastCommaSearch;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Serializer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="depth">The depth.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
private Serializer(object obj, int depth, SerializerOptions options)
|
||||
{
|
||||
if (depth > 20)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The max depth (20) has been reached. Serializer can not continue.");
|
||||
}
|
||||
|
||||
// Basic Type Handling (nulls, strings, number, date and bool)
|
||||
_result = ResolveBasicType(obj);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_result) == false)
|
||||
return;
|
||||
|
||||
_options = options;
|
||||
_lastCommaSearch = FieldSeparatorChar + (_options.Format ? Environment.NewLine : string.Empty);
|
||||
|
||||
// Handle circular references correctly and avoid them
|
||||
if (options.IsObjectPresent(obj))
|
||||
{
|
||||
_result = $"{{ \"$circref\": \"{Escape(obj.GetHashCode().ToStringInvariant(), false)}\" }}";
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, we will need to construct the object with a StringBuilder.
|
||||
_builder = new StringBuilder();
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case IDictionary itemsZero when itemsZero.Count == 0:
|
||||
_result = EmptyObjectLiteral;
|
||||
break;
|
||||
case IDictionary items:
|
||||
_result = ResolveDictionary(items, depth);
|
||||
break;
|
||||
case IEnumerable enumerableZero when !enumerableZero.Cast<object>().Any():
|
||||
_result = EmptyArrayLiteral;
|
||||
break;
|
||||
case IEnumerable enumerableBytes when enumerableBytes is byte[] bytes:
|
||||
_result = Serialize(bytes.ToBase64(), depth, _options);
|
||||
break;
|
||||
case IEnumerable enumerable:
|
||||
_result = ResolveEnumerable(enumerable, depth);
|
||||
break;
|
||||
default:
|
||||
_result = ResolveObject(obj, depth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Serialize(object obj, int depth, SerializerOptions options)
|
||||
{
|
||||
return new Serializer(obj, depth, options)._result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static string ResolveBasicType(object obj)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case null:
|
||||
return NullLiteral;
|
||||
case string s:
|
||||
return Escape(s, true);
|
||||
case bool b:
|
||||
return b ? TrueLiteral : FalseLiteral;
|
||||
case Type _:
|
||||
case Assembly _:
|
||||
case MethodInfo _:
|
||||
case PropertyInfo _:
|
||||
case EventInfo _:
|
||||
return Escape(obj.ToString(), true);
|
||||
case DateTime d:
|
||||
return $"{StringQuotedChar}{d:s}{StringQuotedChar}";
|
||||
default:
|
||||
var targetType = obj.GetType();
|
||||
|
||||
if (!Definitions.BasicTypesInfo.ContainsKey(targetType))
|
||||
return string.Empty;
|
||||
|
||||
var escapedValue = Escape(Definitions.BasicTypesInfo[targetType].ToStringInvariant(obj), false);
|
||||
|
||||
return decimal.TryParse(escapedValue, out _)
|
||||
? $"{escapedValue}"
|
||||
: $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNonEmptyJsonArrayOrObject(string serialized)
|
||||
{
|
||||
if (serialized.Equals(EmptyObjectLiteral) || serialized.Equals(EmptyArrayLiteral)) return false;
|
||||
|
||||
// find the first position the character is not a space
|
||||
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
|
||||
}
|
||||
|
||||
private static string Escape(string str, bool quoted)
|
||||
{
|
||||
if (str == null)
|
||||
return string.Empty;
|
||||
|
||||
var builder = new StringBuilder(str.Length * 2);
|
||||
if (quoted) builder.Append(StringQuotedChar);
|
||||
Escape(str, builder);
|
||||
if (quoted) builder.Append(StringQuotedChar);
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void Escape(string str, StringBuilder builder)
|
||||
{
|
||||
foreach (var currentChar in str)
|
||||
{
|
||||
switch (currentChar)
|
||||
{
|
||||
case '\\':
|
||||
case '"':
|
||||
case '/':
|
||||
builder
|
||||
.Append('\\')
|
||||
.Append(currentChar);
|
||||
break;
|
||||
case '\b':
|
||||
builder.Append("\\b");
|
||||
break;
|
||||
case '\t':
|
||||
builder.Append("\\t");
|
||||
break;
|
||||
case '\n':
|
||||
builder.Append("\\n");
|
||||
break;
|
||||
case '\f':
|
||||
builder.Append("\\f");
|
||||
break;
|
||||
case '\r':
|
||||
builder.Append("\\r");
|
||||
break;
|
||||
default:
|
||||
if (currentChar < ' ')
|
||||
{
|
||||
var escapeBytes = BitConverter.GetBytes((ushort)currentChar);
|
||||
if (BitConverter.IsLittleEndian == false)
|
||||
Array.Reverse(escapeBytes);
|
||||
|
||||
builder.Append("\\u")
|
||||
private class Serializer {
|
||||
#region Private Declarations
|
||||
|
||||
private static readonly Dictionary<Int32, String> IndentStrings = new Dictionary<Int32, String>();
|
||||
|
||||
private readonly SerializerOptions _options;
|
||||
private readonly String _result;
|
||||
private readonly StringBuilder _builder;
|
||||
private readonly String _lastCommaSearch;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Serializer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="depth">The depth.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
private Serializer(Object obj, Int32 depth, SerializerOptions options) {
|
||||
if(depth > 20) {
|
||||
throw new InvalidOperationException(
|
||||
"The max depth (20) has been reached. Serializer can not continue.");
|
||||
}
|
||||
|
||||
// Basic Type Handling (nulls, strings, number, date and bool)
|
||||
this._result = ResolveBasicType(obj);
|
||||
|
||||
if(String.IsNullOrWhiteSpace(this._result) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._options = options;
|
||||
this._lastCommaSearch = FieldSeparatorChar + (this._options.Format ? Environment.NewLine : String.Empty);
|
||||
|
||||
// Handle circular references correctly and avoid them
|
||||
if(options.IsObjectPresent(obj)) {
|
||||
this._result = $"{{ \"$circref\": \"{Escape(obj.GetHashCode().ToStringInvariant(), false)}\" }}";
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, we will need to construct the object with a StringBuilder.
|
||||
this._builder = new StringBuilder();
|
||||
|
||||
switch(obj) {
|
||||
case IDictionary itemsZero when itemsZero.Count == 0:
|
||||
this._result = EmptyObjectLiteral;
|
||||
break;
|
||||
case IDictionary items:
|
||||
this._result = this.ResolveDictionary(items, depth);
|
||||
break;
|
||||
case IEnumerable enumerableZero when !enumerableZero.Cast<Object>().Any():
|
||||
this._result = EmptyArrayLiteral;
|
||||
break;
|
||||
case IEnumerable enumerableBytes when enumerableBytes is Byte[] bytes:
|
||||
this._result = Serialize(bytes.ToBase64(), depth, this._options);
|
||||
break;
|
||||
case IEnumerable enumerable:
|
||||
this._result = this.ResolveEnumerable(enumerable, depth);
|
||||
break;
|
||||
default:
|
||||
this._result = this.ResolveObject(obj, depth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static String Serialize(Object obj, Int32 depth, SerializerOptions options) => new Serializer(obj, depth, options)._result;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static String ResolveBasicType(Object obj) {
|
||||
switch(obj) {
|
||||
case null:
|
||||
return NullLiteral;
|
||||
case String s:
|
||||
return Escape(s, true);
|
||||
case Boolean b:
|
||||
return b ? TrueLiteral : FalseLiteral;
|
||||
case Type _:
|
||||
case Assembly _:
|
||||
case MethodInfo _:
|
||||
case PropertyInfo _:
|
||||
case EventInfo _:
|
||||
return Escape(obj.ToString(), true);
|
||||
case DateTime d:
|
||||
return $"{StringQuotedChar}{d:s}{StringQuotedChar}";
|
||||
default:
|
||||
Type targetType = obj.GetType();
|
||||
|
||||
if(!Definitions.BasicTypesInfo.ContainsKey(targetType)) {
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
String escapedValue = Escape(Definitions.BasicTypesInfo[targetType].ToStringInvariant(obj), false);
|
||||
|
||||
return Decimal.TryParse(escapedValue, out _)
|
||||
? $"{escapedValue}"
|
||||
: $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean IsNonEmptyJsonArrayOrObject(String serialized) {
|
||||
if(serialized.Equals(EmptyObjectLiteral) || serialized.Equals(EmptyArrayLiteral)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// find the first position the character is not a space
|
||||
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
|
||||
}
|
||||
|
||||
private static String Escape(String str, Boolean quoted) {
|
||||
if(str == null) {
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder(str.Length * 2);
|
||||
if(quoted) {
|
||||
_ = builder.Append(StringQuotedChar);
|
||||
}
|
||||
|
||||
Escape(str, builder);
|
||||
if(quoted) {
|
||||
_ = builder.Append(StringQuotedChar);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void Escape(String str, StringBuilder builder) {
|
||||
foreach(Char currentChar in str) {
|
||||
switch(currentChar) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '/':
|
||||
_ = builder
|
||||
.Append('\\')
|
||||
.Append(currentChar);
|
||||
break;
|
||||
case '\b':
|
||||
_ = builder.Append("\\b");
|
||||
break;
|
||||
case '\t':
|
||||
_ = builder.Append("\\t");
|
||||
break;
|
||||
case '\n':
|
||||
_ = builder.Append("\\n");
|
||||
break;
|
||||
case '\f':
|
||||
_ = builder.Append("\\f");
|
||||
break;
|
||||
case '\r':
|
||||
_ = builder.Append("\\r");
|
||||
break;
|
||||
default:
|
||||
if(currentChar < ' ') {
|
||||
Byte[] escapeBytes = BitConverter.GetBytes((UInt16)currentChar);
|
||||
if(BitConverter.IsLittleEndian == false) {
|
||||
Array.Reverse(escapeBytes);
|
||||
}
|
||||
|
||||
_ = builder.Append("\\u")
|
||||
.Append(escapeBytes[1].ToString("X").PadLeft(2, '0'))
|
||||
.Append(escapeBytes[0].ToString("X").PadLeft(2, '0'));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(currentChar);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, object> CreateDictionary(
|
||||
Dictionary<string, MemberInfo> fields,
|
||||
string targetType,
|
||||
object target)
|
||||
{
|
||||
// Create the dictionary and extract the properties
|
||||
var objectDictionary = new Dictionary<string, object>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_options.TypeSpecifier) == false)
|
||||
objectDictionary[_options.TypeSpecifier] = targetType;
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
// Build the dictionary using property names and values
|
||||
// Note: used to be: property.GetValue(target); but we would be reading private properties
|
||||
try
|
||||
{
|
||||
objectDictionary[field.Key] = field.Value is PropertyInfo property
|
||||
? property.GetCacheGetMethod(_options.IncludeNonPublic)(target)
|
||||
: (field.Value as FieldInfo)?.GetValue(target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
|
||||
return objectDictionary;
|
||||
}
|
||||
|
||||
private string ResolveDictionary(IDictionary items, int depth)
|
||||
{
|
||||
Append(OpenObjectChar, depth);
|
||||
AppendLine();
|
||||
|
||||
// Iterate through the elements and output recursively
|
||||
var writeCount = 0;
|
||||
foreach (var key in items.Keys)
|
||||
{
|
||||
// Serialize and append the key (first char indented)
|
||||
Append(StringQuotedChar, depth + 1);
|
||||
Escape(key.ToString(), _builder);
|
||||
_builder
|
||||
.Append(escapeBytes[0].ToString("X").PadLeft(2, '0'));
|
||||
} else {
|
||||
_ = builder.Append(currentChar);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<String, Object> CreateDictionary(
|
||||
Dictionary<String, MemberInfo> fields,
|
||||
String targetType,
|
||||
Object target) {
|
||||
// Create the dictionary and extract the properties
|
||||
Dictionary<String, Object> objectDictionary = new Dictionary<String, Object>();
|
||||
|
||||
if(String.IsNullOrWhiteSpace(this._options.TypeSpecifier) == false) {
|
||||
objectDictionary[this._options.TypeSpecifier] = targetType;
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<String, MemberInfo> field in fields) {
|
||||
// Build the dictionary using property names and values
|
||||
// Note: used to be: property.GetValue(target); but we would be reading private properties
|
||||
try {
|
||||
objectDictionary[field.Key] = field.Value is PropertyInfo property
|
||||
? property.GetCacheGetMethod(this._options.IncludeNonPublic)(target)
|
||||
: (field.Value as FieldInfo)?.GetValue(target);
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
|
||||
return objectDictionary;
|
||||
}
|
||||
|
||||
private String ResolveDictionary(IDictionary items, Int32 depth) {
|
||||
this.Append(OpenObjectChar, depth);
|
||||
this.AppendLine();
|
||||
|
||||
// Iterate through the elements and output recursively
|
||||
Int32 writeCount = 0;
|
||||
foreach(Object key in items.Keys) {
|
||||
// Serialize and append the key (first char indented)
|
||||
this.Append(StringQuotedChar, depth + 1);
|
||||
Escape(key.ToString(), this._builder);
|
||||
_ = this._builder
|
||||
.Append(StringQuotedChar)
|
||||
.Append(ValueSeparatorChar)
|
||||
.Append(" ");
|
||||
|
||||
// Serialize and append the value
|
||||
var serializedValue = Serialize(items[key], depth + 1, _options);
|
||||
|
||||
if (IsNonEmptyJsonArrayOrObject(serializedValue)) AppendLine();
|
||||
Append(serializedValue, 0);
|
||||
|
||||
// Add a comma and start a new line -- We will remove the last one when we are done writing the elements
|
||||
Append(FieldSeparatorChar, 0);
|
||||
AppendLine();
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
// Output the end of the object and set the result
|
||||
RemoveLastComma();
|
||||
Append(CloseObjectChar, writeCount > 0 ? depth : 0);
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
private string ResolveObject(object target, int depth)
|
||||
{
|
||||
var targetType = target.GetType();
|
||||
var fields = _options.GetProperties(targetType);
|
||||
|
||||
if (fields.Count == 0 && string.IsNullOrWhiteSpace(_options.TypeSpecifier))
|
||||
return EmptyObjectLiteral;
|
||||
|
||||
// If we arrive here, then we convert the object into a
|
||||
// dictionary of property names and values and call the serialization
|
||||
// function again
|
||||
var objectDictionary = CreateDictionary(fields, targetType.ToString(), target);
|
||||
|
||||
return Serialize(objectDictionary, depth, _options);
|
||||
}
|
||||
|
||||
private string ResolveEnumerable(IEnumerable target, int depth)
|
||||
{
|
||||
// Cast the items as a generic object array
|
||||
var items = target.Cast<object>();
|
||||
|
||||
Append(OpenArrayChar, depth);
|
||||
AppendLine();
|
||||
|
||||
// Iterate through the elements and output recursively
|
||||
var writeCount = 0;
|
||||
foreach (var entry in items)
|
||||
{
|
||||
var serializedValue = Serialize(entry, depth + 1, _options);
|
||||
|
||||
if (IsNonEmptyJsonArrayOrObject(serializedValue))
|
||||
Append(serializedValue, 0);
|
||||
else
|
||||
Append(serializedValue, depth + 1);
|
||||
|
||||
Append(FieldSeparatorChar, 0);
|
||||
AppendLine();
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
// Output the end of the array and set the result
|
||||
RemoveLastComma();
|
||||
Append(CloseArrayChar, writeCount > 0 ? depth : 0);
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
private void SetIndent(int depth)
|
||||
{
|
||||
if (_options.Format == false || depth <= 0) return;
|
||||
|
||||
_builder.Append(IndentStrings.GetOrAdd(depth, x => new string(' ', x * 4)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last comma in the current string builder.
|
||||
/// </summary>
|
||||
private void RemoveLastComma()
|
||||
{
|
||||
if (_builder.Length < _lastCommaSearch.Length)
|
||||
return;
|
||||
|
||||
if (_lastCommaSearch.Where((t, i) => _builder[_builder.Length - _lastCommaSearch.Length + i] != t).Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got this far, we simply remove the comma character
|
||||
_builder.Remove(_builder.Length - _lastCommaSearch.Length, 1);
|
||||
}
|
||||
|
||||
private void Append(string text, int depth)
|
||||
{
|
||||
SetIndent(depth);
|
||||
_builder.Append(text);
|
||||
}
|
||||
|
||||
private void Append(char text, int depth)
|
||||
{
|
||||
SetIndent(depth);
|
||||
_builder.Append(text);
|
||||
}
|
||||
|
||||
private void AppendLine()
|
||||
{
|
||||
if (_options.Format == false) return;
|
||||
_builder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
.Append(" ");
|
||||
|
||||
// Serialize and append the value
|
||||
String serializedValue = Serialize(items[key], depth + 1, this._options);
|
||||
|
||||
if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
|
||||
this.AppendLine();
|
||||
}
|
||||
|
||||
this.Append(serializedValue, 0);
|
||||
|
||||
// Add a comma and start a new line -- We will remove the last one when we are done writing the elements
|
||||
this.Append(FieldSeparatorChar, 0);
|
||||
this.AppendLine();
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
// Output the end of the object and set the result
|
||||
this.RemoveLastComma();
|
||||
this.Append(CloseObjectChar, writeCount > 0 ? depth : 0);
|
||||
return this._builder.ToString();
|
||||
}
|
||||
|
||||
private String ResolveObject(Object target, Int32 depth) {
|
||||
Type targetType = target.GetType();
|
||||
Dictionary<String, MemberInfo> fields = this._options.GetProperties(targetType);
|
||||
|
||||
if(fields.Count == 0 && String.IsNullOrWhiteSpace(this._options.TypeSpecifier)) {
|
||||
return EmptyObjectLiteral;
|
||||
}
|
||||
|
||||
// If we arrive here, then we convert the object into a
|
||||
// dictionary of property names and values and call the serialization
|
||||
// function again
|
||||
Dictionary<String, Object> objectDictionary = this.CreateDictionary(fields, targetType.ToString(), target);
|
||||
|
||||
return Serialize(objectDictionary, depth, this._options);
|
||||
}
|
||||
|
||||
private String ResolveEnumerable(IEnumerable target, Int32 depth) {
|
||||
// Cast the items as a generic object array
|
||||
IEnumerable<Object> items = target.Cast<Object>();
|
||||
|
||||
this.Append(OpenArrayChar, depth);
|
||||
this.AppendLine();
|
||||
|
||||
// Iterate through the elements and output recursively
|
||||
Int32 writeCount = 0;
|
||||
foreach(Object entry in items) {
|
||||
String serializedValue = Serialize(entry, depth + 1, this._options);
|
||||
|
||||
if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
|
||||
this.Append(serializedValue, 0);
|
||||
} else {
|
||||
this.Append(serializedValue, depth + 1);
|
||||
}
|
||||
|
||||
this.Append(FieldSeparatorChar, 0);
|
||||
this.AppendLine();
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
// Output the end of the array and set the result
|
||||
this.RemoveLastComma();
|
||||
this.Append(CloseArrayChar, writeCount > 0 ? depth : 0);
|
||||
return this._builder.ToString();
|
||||
}
|
||||
|
||||
private void SetIndent(Int32 depth) {
|
||||
if(this._options.Format == false || depth <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = this._builder.Append(IndentStrings.GetOrAdd(depth, x => new String(' ', x * 4)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last comma in the current string builder.
|
||||
/// </summary>
|
||||
private void RemoveLastComma() {
|
||||
if(this._builder.Length < this._lastCommaSearch.Length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this._lastCommaSearch.Where((t, i) => this._builder[this._builder.Length - this._lastCommaSearch.Length + i] != t).Any()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got this far, we simply remove the comma character
|
||||
_ = this._builder.Remove(this._builder.Length - this._lastCommaSearch.Length, 1);
|
||||
}
|
||||
|
||||
private void Append(String text, Int32 depth) {
|
||||
this.SetIndent(depth);
|
||||
_ = this._builder.Append(text);
|
||||
}
|
||||
|
||||
private void Append(Char text, Int32 depth) {
|
||||
this.SetIndent(depth);
|
||||
_ = this._builder.Append(text);
|
||||
}
|
||||
|
||||
private void AppendLine() {
|
||||
if(this._options.Format == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = this._builder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,107 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public partial class Json
|
||||
{
|
||||
private class SerializerOptions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>>
|
||||
TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>>();
|
||||
|
||||
private readonly string[] _includeProperties;
|
||||
private readonly string[] _excludeProperties;
|
||||
private readonly Dictionary<int, List<WeakReference>> _parentReferences = new Dictionary<int, List<WeakReference>>();
|
||||
|
||||
public SerializerOptions(
|
||||
bool format,
|
||||
string typeSpecifier,
|
||||
string[] includeProperties,
|
||||
string[] excludeProperties = null,
|
||||
bool includeNonPublic = true,
|
||||
IReadOnlyCollection<WeakReference> parentReferences = null)
|
||||
{
|
||||
_includeProperties = includeProperties;
|
||||
_excludeProperties = excludeProperties;
|
||||
|
||||
IncludeNonPublic = includeNonPublic;
|
||||
Format = format;
|
||||
TypeSpecifier = typeSpecifier;
|
||||
|
||||
if (parentReferences == null)
|
||||
return;
|
||||
|
||||
foreach (var parentReference in parentReferences.Where(x => x.IsAlive))
|
||||
{
|
||||
IsObjectPresent(parentReference.Target);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Format { get; }
|
||||
public string TypeSpecifier { get; }
|
||||
public bool IncludeNonPublic { get; }
|
||||
|
||||
internal bool IsObjectPresent(object target)
|
||||
{
|
||||
var hashCode = target.GetHashCode();
|
||||
|
||||
if (_parentReferences.ContainsKey(hashCode))
|
||||
{
|
||||
if (_parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target)))
|
||||
return true;
|
||||
|
||||
_parentReferences[hashCode].Add(new WeakReference(target));
|
||||
return false;
|
||||
}
|
||||
|
||||
_parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
|
||||
return false;
|
||||
}
|
||||
|
||||
internal Dictionary<string, MemberInfo> GetProperties(Type targetType)
|
||||
=> GetPropertiesCache(targetType)
|
||||
.When(() => _includeProperties?.Length > 0,
|
||||
query => query.Where(p => _includeProperties.Contains(p.Key.Item1)))
|
||||
.When(() => _excludeProperties?.Length > 0,
|
||||
query => query.Where(p => !_excludeProperties.Contains(p.Key.Item1)))
|
||||
.ToDictionary(x => x.Key.Item2, x => x.Value);
|
||||
|
||||
private static Dictionary<Tuple<string, string>, MemberInfo> GetPropertiesCache(Type targetType)
|
||||
{
|
||||
if (TypeCache.TryGetValue(targetType, out var current))
|
||||
return current;
|
||||
|
||||
var fields =
|
||||
new List<MemberInfo>(PropertyTypeCache.RetrieveAllProperties(targetType).Where(p => p.CanRead));
|
||||
|
||||
// If the target is a struct (value type) navigate the fields.
|
||||
if (targetType.IsValueType())
|
||||
{
|
||||
fields.AddRange(FieldTypeCache.RetrieveAllFields(targetType));
|
||||
}
|
||||
|
||||
var value = fields
|
||||
.ToDictionary(
|
||||
x => Tuple.Create(x.Name,
|
||||
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name),
|
||||
x => x);
|
||||
|
||||
TypeCache.TryAdd(targetType, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Attributes;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public partial class Json {
|
||||
private class SerializerOptions {
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>
|
||||
TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>();
|
||||
|
||||
private readonly String[] _includeProperties;
|
||||
private readonly String[] _excludeProperties;
|
||||
private readonly Dictionary<Int32, List<WeakReference>> _parentReferences = new Dictionary<Int32, List<WeakReference>>();
|
||||
|
||||
public SerializerOptions(
|
||||
Boolean format,
|
||||
String typeSpecifier,
|
||||
String[] includeProperties,
|
||||
String[] excludeProperties = null,
|
||||
Boolean includeNonPublic = true,
|
||||
IReadOnlyCollection<WeakReference> parentReferences = null) {
|
||||
this._includeProperties = includeProperties;
|
||||
this._excludeProperties = excludeProperties;
|
||||
|
||||
this.IncludeNonPublic = includeNonPublic;
|
||||
this.Format = format;
|
||||
this.TypeSpecifier = typeSpecifier;
|
||||
|
||||
if(parentReferences == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(WeakReference parentReference in parentReferences.Where(x => x.IsAlive)) {
|
||||
_ = this.IsObjectPresent(parentReference.Target);
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean Format {
|
||||
get;
|
||||
}
|
||||
public String TypeSpecifier {
|
||||
get;
|
||||
}
|
||||
public Boolean IncludeNonPublic {
|
||||
get;
|
||||
}
|
||||
|
||||
internal Boolean IsObjectPresent(Object target) {
|
||||
Int32 hashCode = target.GetHashCode();
|
||||
|
||||
if(this._parentReferences.ContainsKey(hashCode)) {
|
||||
if(this._parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this._parentReferences[hashCode].Add(new WeakReference(target));
|
||||
return false;
|
||||
}
|
||||
|
||||
this._parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
|
||||
return false;
|
||||
}
|
||||
|
||||
internal Dictionary<String, MemberInfo> GetProperties(Type targetType)
|
||||
=> GetPropertiesCache(targetType)
|
||||
.When(() => this._includeProperties?.Length > 0,
|
||||
query => query.Where(p => this._includeProperties.Contains(p.Key.Item1)))
|
||||
.When(() => this._excludeProperties?.Length > 0,
|
||||
query => query.Where(p => !this._excludeProperties.Contains(p.Key.Item1)))
|
||||
.ToDictionary(x => x.Key.Item2, x => x.Value);
|
||||
|
||||
private static Dictionary<Tuple<String, String>, MemberInfo> GetPropertiesCache(Type targetType) {
|
||||
if(TypeCache.TryGetValue(targetType, out Dictionary<Tuple<String, String>, MemberInfo> current)) {
|
||||
return current;
|
||||
}
|
||||
|
||||
List<MemberInfo> fields =
|
||||
new List<MemberInfo>(PropertyTypeCache.RetrieveAllProperties(targetType).Where(p => p.CanRead));
|
||||
|
||||
// If the target is a struct (value type) navigate the fields.
|
||||
if(targetType.IsValueType()) {
|
||||
fields.AddRange(FieldTypeCache.RetrieveAllFields(targetType));
|
||||
}
|
||||
|
||||
Dictionary<Tuple<String, String>, MemberInfo> value = fields
|
||||
.ToDictionary(
|
||||
x => Tuple.Create(x.Name,
|
||||
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name),
|
||||
x => x);
|
||||
|
||||
_ = TypeCache.TryAdd(targetType, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,331 +1,319 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using Reflection;
|
||||
using System;
|
||||
using Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Attributes;
|
||||
|
||||
using Unosquare.Swan.Reflection;
|
||||
using System;
|
||||
using Unosquare.Swan.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Attributes;
|
||||
|
||||
namespace Unosquare.Swan.Formatters {
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public static partial class Json {
|
||||
#region Constants
|
||||
|
||||
internal const String AddMethodName = "Add";
|
||||
|
||||
private const Char OpenObjectChar = '{';
|
||||
private const Char CloseObjectChar = '}';
|
||||
|
||||
private const Char OpenArrayChar = '[';
|
||||
private const Char CloseArrayChar = ']';
|
||||
|
||||
private const Char FieldSeparatorChar = ',';
|
||||
private const Char ValueSeparatorChar = ':';
|
||||
|
||||
private const Char StringEscapeChar = '\\';
|
||||
private const Char StringQuotedChar = '"';
|
||||
|
||||
private const String EmptyObjectLiteral = "{ }";
|
||||
private const String EmptyArrayLiteral = "[ ]";
|
||||
private const String TrueLiteral = "true";
|
||||
private const String FalseLiteral = "false";
|
||||
private const String NullLiteral = "null";
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly PropertyTypeCache PropertyTypeCache = new PropertyTypeCache();
|
||||
private static readonly FieldTypeCache FieldTypeCache = new FieldTypeCache();
|
||||
private static readonly CollectionCacheRepository<String> IgnoredPropertiesCache = new CollectionCacheRepository<String>();
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// Serializes the specified object into a JSON string.
|
||||
/// </summary>
|
||||
public static partial class Json
|
||||
{
|
||||
#region Constants
|
||||
|
||||
internal const string AddMethodName = "Add";
|
||||
|
||||
private const char OpenObjectChar = '{';
|
||||
private const char CloseObjectChar = '}';
|
||||
|
||||
private const char OpenArrayChar = '[';
|
||||
private const char CloseArrayChar = ']';
|
||||
|
||||
private const char FieldSeparatorChar = ',';
|
||||
private const char ValueSeparatorChar = ':';
|
||||
|
||||
private const char StringEscapeChar = '\\';
|
||||
private const char StringQuotedChar = '"';
|
||||
|
||||
private const string EmptyObjectLiteral = "{ }";
|
||||
private const string EmptyArrayLiteral = "[ ]";
|
||||
private const string TrueLiteral = "true";
|
||||
private const string FalseLiteral = "false";
|
||||
private const string NullLiteral = "null";
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly PropertyTypeCache PropertyTypeCache = new PropertyTypeCache();
|
||||
private static readonly FieldTypeCache FieldTypeCache = new FieldTypeCache();
|
||||
private static readonly CollectionCacheRepository<string> IgnoredPropertiesCache = new CollectionCacheRepository<string>();
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object into a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
|
||||
/// <param name="includedNames">The included property names.</param>
|
||||
/// <param name="excludedNames">The excluded property names.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents the current object.
|
||||
/// </returns>
|
||||
/// <example>
|
||||
/// The following example describes how to serialize a simple object.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new { One = "One", Two = "Two" };
|
||||
///
|
||||
/// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class JsonPropertyExample
|
||||
/// {
|
||||
/// [JsonProperty("data")]
|
||||
/// public string Data { get; set; }
|
||||
///
|
||||
/// [JsonProperty("ignoredData", true)]
|
||||
/// public string IgnoredData { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" };
|
||||
///
|
||||
/// // {"data": "OK"}
|
||||
/// var serializedObj = Json.Serialize(obj);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string Serialize(
|
||||
object obj,
|
||||
bool format = false,
|
||||
string typeSpecifier = null,
|
||||
bool includeNonPublic = false,
|
||||
string[] includedNames = null,
|
||||
string[] excludedNames = null)
|
||||
{
|
||||
return Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object into a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
|
||||
/// <param name="includedNames">The included property names.</param>
|
||||
/// <param name="excludedNames">The excluded property names.</param>
|
||||
/// <param name="parentReferences">The parent references.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents the current object.
|
||||
/// </returns>
|
||||
public static string Serialize(
|
||||
object obj,
|
||||
bool format,
|
||||
string typeSpecifier,
|
||||
bool includeNonPublic,
|
||||
string[] includedNames,
|
||||
string[] excludedNames,
|
||||
List<WeakReference> parentReferences)
|
||||
{
|
||||
if (obj != null && (obj is string || Definitions.AllBasicValueTypes.Contains(obj.GetType())))
|
||||
{
|
||||
return SerializePrimitiveValue(obj);
|
||||
}
|
||||
|
||||
var options = new SerializerOptions(
|
||||
format,
|
||||
typeSpecifier,
|
||||
includedNames,
|
||||
GetExcludedNames(obj?.GetType(), excludedNames),
|
||||
includeNonPublic,
|
||||
parentReferences);
|
||||
|
||||
return Serializer.Serialize(obj, 0, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object only including the specified property names.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="includeNames">The include names.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
/// <example>
|
||||
/// The following example shows how to serialize a simple object including the specified properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // object to serialize
|
||||
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
|
||||
///
|
||||
/// // the included names
|
||||
/// var includedNames = new[] { "Two", "Three" };
|
||||
///
|
||||
/// // serialize only the included names
|
||||
/// var data = Json.SerializeOnly(basicObject, true, includedNames);
|
||||
/// // {"Two": "Two","Three": "Three" }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string SerializeOnly(object obj, bool format, params string[] includeNames)
|
||||
{
|
||||
var options = new SerializerOptions(format, null, includeNames);
|
||||
|
||||
return Serializer.Serialize(obj, 0, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object excluding the specified property names.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="excludeNames">The exclude names.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
/// <example>
|
||||
/// The following code shows how to serialize a simple object exluding the specified properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // object to serialize
|
||||
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
|
||||
///
|
||||
/// // the excluded names
|
||||
/// var excludeNames = new[] { "Two", "Three" };
|
||||
///
|
||||
/// // serialize excluding
|
||||
/// var data = Json.SerializeExcluding(basicObject, false, includedNames);
|
||||
/// // {"One": "One"}
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string SerializeExcluding(object obj, bool format, params string[] excludeNames)
|
||||
{
|
||||
var options = new SerializerOptions(format, null, null, excludeNames);
|
||||
|
||||
return Serializer.Serialize(obj, 0, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
|
||||
/// depending on the syntax of the JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <returns>Type of the current deserializes.</returns>
|
||||
/// <example>
|
||||
/// The following code shows how to deserialize a JSON string into a Dictionary.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json to deserialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
///
|
||||
/// // deserializes the specified json into a Dictionary<string, object>.
|
||||
/// var data = Json.Deserialize(basicJson);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static object Deserialize(string json) => Deserializer.DeserializeInternal(json);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string and converts it to the specified object type.
|
||||
/// Non-public constructors and property setters are ignored.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to deserialize.</typeparam>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <returns>The deserialized specified type object.</returns>
|
||||
/// <example>
|
||||
/// The following code describes how to deserialize a JSON string into an object of type T.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json type BasicJson to serialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
///
|
||||
/// // deserializes the specified string in a new instance of the type BasicJson.
|
||||
/// var data = Json.Deserialize<BasicJson>(basicJson);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static T Deserialize<T>(string json) => (T)Deserialize(json, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string and converts it to the specified object type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to deserialize.</typeparam>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
|
||||
/// <returns>The deserialized specified type object.</returns>
|
||||
public static T Deserialize<T>(string json, bool includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string and converts it to the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <param name="resultType">Type of the result.</param>
|
||||
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
|
||||
/// <returns>Type of the current conversion from json result.</returns>
|
||||
public static object Deserialize(string json, Type resultType, bool includeNonPublic = false)
|
||||
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), resultType, includeNonPublic);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private API
|
||||
|
||||
private static string[] GetExcludedNames(Type type, string[] excludedNames)
|
||||
{
|
||||
if (type == null)
|
||||
return excludedNames;
|
||||
|
||||
var excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
|
||||
/// <param name="includedNames">The included property names.</param>
|
||||
/// <param name="excludedNames">The excluded property names.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents the current object.
|
||||
/// </returns>
|
||||
/// <example>
|
||||
/// The following example describes how to serialize a simple object.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new { One = "One", Two = "Two" };
|
||||
///
|
||||
/// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class JsonPropertyExample
|
||||
/// {
|
||||
/// [JsonProperty("data")]
|
||||
/// public string Data { get; set; }
|
||||
///
|
||||
/// [JsonProperty("ignoredData", true)]
|
||||
/// public string IgnoredData { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" };
|
||||
///
|
||||
/// // {"data": "OK"}
|
||||
/// var serializedObj = Json.Serialize(obj);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static String Serialize(
|
||||
Object obj,
|
||||
Boolean format = false,
|
||||
String typeSpecifier = null,
|
||||
Boolean includeNonPublic = false,
|
||||
String[] includedNames = null,
|
||||
String[] excludedNames = null) => Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null);
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object into a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
|
||||
/// <param name="includedNames">The included property names.</param>
|
||||
/// <param name="excludedNames">The excluded property names.</param>
|
||||
/// <param name="parentReferences">The parent references.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents the current object.
|
||||
/// </returns>
|
||||
public static String Serialize(
|
||||
Object obj,
|
||||
Boolean format,
|
||||
String typeSpecifier,
|
||||
Boolean includeNonPublic,
|
||||
String[] includedNames,
|
||||
String[] excludedNames,
|
||||
List<WeakReference> parentReferences) {
|
||||
if(obj != null && (obj is String || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) {
|
||||
return SerializePrimitiveValue(obj);
|
||||
}
|
||||
|
||||
SerializerOptions options = new SerializerOptions(
|
||||
format,
|
||||
typeSpecifier,
|
||||
includedNames,
|
||||
GetExcludedNames(obj?.GetType(), excludedNames),
|
||||
includeNonPublic,
|
||||
parentReferences);
|
||||
|
||||
return Serializer.Serialize(obj, 0, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object only including the specified property names.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="includeNames">The include names.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
/// <example>
|
||||
/// The following example shows how to serialize a simple object including the specified properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // object to serialize
|
||||
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
|
||||
///
|
||||
/// // the included names
|
||||
/// var includedNames = new[] { "Two", "Three" };
|
||||
///
|
||||
/// // serialize only the included names
|
||||
/// var data = Json.SerializeOnly(basicObject, true, includedNames);
|
||||
/// // {"Two": "Two","Three": "Three" }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static String SerializeOnly(Object obj, Boolean format, params String[] includeNames) {
|
||||
SerializerOptions options = new SerializerOptions(format, null, includeNames);
|
||||
|
||||
return Serializer.Serialize(obj, 0, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object excluding the specified property names.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
|
||||
/// <param name="excludeNames">The exclude names.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
/// <example>
|
||||
/// The following code shows how to serialize a simple object exluding the specified properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // object to serialize
|
||||
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
|
||||
///
|
||||
/// // the excluded names
|
||||
/// var excludeNames = new[] { "Two", "Three" };
|
||||
///
|
||||
/// // serialize excluding
|
||||
/// var data = Json.SerializeExcluding(basicObject, false, includedNames);
|
||||
/// // {"One": "One"}
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static String SerializeExcluding(Object obj, Boolean format, params String[] excludeNames) {
|
||||
SerializerOptions options = new SerializerOptions(format, null, null, excludeNames);
|
||||
|
||||
return Serializer.Serialize(obj, 0, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
|
||||
/// depending on the syntax of the JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <returns>Type of the current deserializes.</returns>
|
||||
/// <example>
|
||||
/// The following code shows how to deserialize a JSON string into a Dictionary.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json to deserialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
///
|
||||
/// // deserializes the specified json into a Dictionary<string, object>.
|
||||
/// var data = Json.Deserialize(basicJson);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Object Deserialize(String json) => Deserializer.DeserializeInternal(json);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string and converts it to the specified object type.
|
||||
/// Non-public constructors and property setters are ignored.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to deserialize.</typeparam>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <returns>The deserialized specified type object.</returns>
|
||||
/// <example>
|
||||
/// The following code describes how to deserialize a JSON string into an object of type T.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json type BasicJson to serialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
///
|
||||
/// // deserializes the specified string in a new instance of the type BasicJson.
|
||||
/// var data = Json.Deserialize<BasicJson>(basicJson);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static T Deserialize<T>(String json) => (T)Deserialize(json, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string and converts it to the specified object type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to deserialize.</typeparam>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
|
||||
/// <returns>The deserialized specified type object.</returns>
|
||||
public static T Deserialize<T>(String json, Boolean includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified json string and converts it to the specified object type.
|
||||
/// </summary>
|
||||
/// <param name="json">The json.</param>
|
||||
/// <param name="resultType">Type of the result.</param>
|
||||
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
|
||||
/// <returns>Type of the current conversion from json result.</returns>
|
||||
public static Object Deserialize(String json, Type resultType, Boolean includeNonPublic = false)
|
||||
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), resultType, includeNonPublic);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private API
|
||||
|
||||
private static String[] GetExcludedNames(Type type, String[] excludedNames) {
|
||||
if(type == null) {
|
||||
return excludedNames;
|
||||
}
|
||||
|
||||
IEnumerable<String> excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
|
||||
.Where(x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true)
|
||||
.Select(x => x.Name));
|
||||
|
||||
if (excludedByAttr?.Any() != true)
|
||||
return excludedNames;
|
||||
|
||||
return excludedNames == null
|
||||
.Select(x => x.Name));
|
||||
|
||||
return excludedByAttr?.Any() != true
|
||||
? excludedNames
|
||||
: excludedNames == null
|
||||
? excludedByAttr.ToArray()
|
||||
: excludedByAttr.Intersect(excludedNames).ToArray();
|
||||
}
|
||||
|
||||
private static string SerializePrimitiveValue(object obj)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case string stringValue:
|
||||
return stringValue;
|
||||
case bool boolValue:
|
||||
return boolValue ? TrueLiteral : FalseLiteral;
|
||||
default:
|
||||
return obj.ToString();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
: excludedByAttr.Intersect(excludedNames).ToArray();
|
||||
}
|
||||
|
||||
private static String SerializePrimitiveValue(Object obj) {
|
||||
switch(obj) {
|
||||
case String stringValue:
|
||||
return stringValue;
|
||||
case Boolean boolValue:
|
||||
return boolValue ? TrueLiteral : FalseLiteral;
|
||||
default:
|
||||
return obj.ToString();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user