Coding style

This commit is contained in:
2019-12-04 17:10:06 +01:00
parent c1e8637516
commit 2f74732924
72 changed files with 12543 additions and 13087 deletions
File diff suppressed because it is too large Load Diff
+445 -460
View File
@@ -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&lt;Person&gt;
/// {
/// 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&lt;Person&gt;
/// {
/// 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
}
}
+133 -149
View File
@@ -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}");
}
}
}
}
+299 -332
View File
@@ -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,
}
}
}
}
+341 -353
View File
@@ -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;
}
}
}
}
+314 -326
View File
@@ -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&lt;string, object&gt;.
/// 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&lt;BasicJson&gt;(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&lt;string, object&gt;.
/// 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&lt;BasicJson&gt;(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
}
}