Strip Swan library Part 1
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Swan.Reflection;
|
||||
|
||||
namespace 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, global::System.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 readonly JsonSerializerCase _jsonSerializerCase;
|
||||
|
||||
private Converter(Object? source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic, JsonSerializerCase jsonSerializerCase) {
|
||||
this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
|
||||
this._includeNonPublic = includeNonPublic;
|
||||
this._jsonSerializerCase = jsonSerializerCase;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
internal static Object? FromJsonResult(Object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, Boolean includeNonPublic = false) {
|
||||
Object? nullRef = null;
|
||||
return new Converter(source, targetType ?? typeof(Object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult();
|
||||
}
|
||||
|
||||
private static Object? FromJsonResult(Object source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic) => new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult();
|
||||
|
||||
private static Type? GetAddMethodParameterType(Type targetType) => ListAddMethodCache.GetOrAdd(targetType, x => x.GetMethods().FirstOrDefault(m => m.Name == 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(FormatException) {
|
||||
target = Encoding.UTF8.GetBytes(sourceString);
|
||||
} // Get the string bytes in UTF8
|
||||
}
|
||||
|
||||
private Object GetSourcePropertyValue(IDictionary<String, Object> sourceProperties, MemberInfo targetProperty) {
|
||||
String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name.GetNameWithCase(this._jsonSerializerCase));
|
||||
|
||||
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(IEnumerable<Object> objects, IList list) {
|
||||
Type? parameterType = GetAddMethodParameterType(this._targetType);
|
||||
if(parameterType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(Object item in objects) {
|
||||
try {
|
||||
_ = list.Add(FromJsonResult(item, this._jsonSerializerCase, 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], this._jsonSerializerCase, 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.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 == AddMethodName && m.IsPublic && m.GetParameters().Length == 2);
|
||||
|
||||
// skip if we don't have a compatible add method
|
||||
if(addMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
global::System.Reflection.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, this._jsonSerializerCase, 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) {
|
||||
global::System.Collections.Generic.IEnumerable<global::System.Reflection.PropertyInfo> properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite);
|
||||
|
||||
foreach(PropertyInfo property in properties) {
|
||||
Object sourcePropertyValue = this.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.DefaultCache.Value.RetrieveAllFields(this._targetType)) {
|
||||
Object sourcePropertyValue = this.GetSourcePropertyValue(sourceProperties, field);
|
||||
if(sourcePropertyValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object? targetPropertyValue = FromJsonResult(sourcePropertyValue, this._jsonSerializerCase, field.FieldType, this._includeNonPublic);
|
||||
|
||||
try {
|
||||
field.SetValue(this._target, targetPropertyValue);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace 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 simple JSON Deserializer.
|
||||
/// </summary>
|
||||
private class Deserializer {
|
||||
#region State Variables
|
||||
|
||||
private readonly Object? _result;
|
||||
private readonly String _json;
|
||||
|
||||
private Dictionary<String, Object?>? _resultObject;
|
||||
private List<Object?>? _resultArray;
|
||||
private ReadState _state = ReadState.WaitingForRootOpen;
|
||||
private String? _currentFieldName;
|
||||
|
||||
private Int32 _index;
|
||||
|
||||
#endregion
|
||||
|
||||
private Deserializer(String? json, Int32 startIndex) {
|
||||
if(json == null) {
|
||||
this._json = "";
|
||||
return;
|
||||
}
|
||||
this._json = json;
|
||||
|
||||
for(this._index = startIndex; this._index < this._json.Length; this._index++) {
|
||||
switch(this._state) {
|
||||
case ReadState.WaitingForRootOpen:
|
||||
this.WaitForRootOpen();
|
||||
continue;
|
||||
case ReadState.WaitingForField when Char.IsWhiteSpace(this._json, this._index):
|
||||
continue;
|
||||
case ReadState.WaitingForField when this._resultObject != null && this._json[this._index] == CloseObjectChar || this._resultArray != null && this._json[this._index] == CloseArrayChar:
|
||||
// Handle empty arrays and empty objects
|
||||
this._result = this._resultObject ?? this._resultArray as Object;
|
||||
return;
|
||||
case ReadState.WaitingForField when this._json[this._index] != StringQuotedChar:
|
||||
throw this.CreateParserException($"'{StringQuotedChar}'");
|
||||
case ReadState.WaitingForField: {
|
||||
Int32 charCount = this.GetFieldNameCount();
|
||||
|
||||
this._currentFieldName = Unescape(this._json.SliceLength(this._index + 1, charCount));
|
||||
this._index += charCount + 1;
|
||||
this._state = ReadState.WaitingForColon;
|
||||
continue;
|
||||
}
|
||||
|
||||
case ReadState.WaitingForColon when Char.IsWhiteSpace(this._json, this._index):
|
||||
continue;
|
||||
case ReadState.WaitingForColon when this._json[this._index] != ValueSeparatorChar:
|
||||
throw this.CreateParserException($"'{ValueSeparatorChar}'");
|
||||
case ReadState.WaitingForColon:
|
||||
this._state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
case ReadState.WaitingForValue when Char.IsWhiteSpace(this._json, this._index):
|
||||
continue;
|
||||
case ReadState.WaitingForValue when this._resultObject != null && this._json[this._index] == CloseObjectChar || this._resultArray != null && this._json[this._index] == CloseArrayChar:
|
||||
// Handle empty arrays and empty objects
|
||||
this._result = this._resultObject ?? this._resultArray as Object;
|
||||
return;
|
||||
case ReadState.WaitingForValue:
|
||||
this.ExtractValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this._state != ReadState.WaitingForNextOrRootClose || 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)) {
|
||||
throw this.CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
|
||||
}
|
||||
|
||||
this._result = this._resultObject ?? this._resultArray as Object;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Object? DeserializeInternal(String? json) => new Deserializer(json, 0)._result;
|
||||
|
||||
private void WaitForRootOpen() {
|
||||
if(Char.IsWhiteSpace(this._json, this._index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(this._json[this._index]) {
|
||||
case OpenObjectChar:
|
||||
this._resultObject = new Dictionary<String, Object?>();
|
||||
this._state = ReadState.WaitingForField;
|
||||
return;
|
||||
case OpenArrayChar:
|
||||
this._resultArray = new List<Object?>();
|
||||
this._state = ReadState.WaitingForValue;
|
||||
return;
|
||||
default:
|
||||
throw this.CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractValue() {
|
||||
// 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;
|
||||
}
|
||||
|
||||
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, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, 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) != 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace 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 simple JSON serializer.
|
||||
/// </summary>
|
||||
private class Serializer {
|
||||
#region Private Declarations
|
||||
|
||||
private static readonly Dictionary<Int32, String> IndentStrings = new Dictionary<global::System.Int32, global::System.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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._options = options;
|
||||
|
||||
// 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._lastCommaSearch = FieldSeparatorChar + (this._options.Format ? Environment.NewLine : String.Empty);
|
||||
this._builder = new StringBuilder();
|
||||
|
||||
this._result = obj switch
|
||||
{
|
||||
IDictionary itemsZero when itemsZero.Count == 0 => EmptyObjectLiteral,
|
||||
IDictionary items => this.ResolveDictionary(items, depth),
|
||||
IEnumerable enumerableZero when !enumerableZero.Cast<Object>().Any() => EmptyArrayLiteral,
|
||||
IEnumerable enumerableBytes when enumerableBytes is Byte[] bytes => Serialize(bytes.ToBase64(), depth, this._options),
|
||||
IEnumerable enumerable => this.ResolveEnumerable(enumerable, depth),
|
||||
_ => this.ResolveObject(obj!, depth)
|
||||
};
|
||||
}
|
||||
|
||||
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.Value.ContainsKey(targetType)) {
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
String escapedValue = Escape(Definitions.BasicTypesInfo.Value[targetType].ToStringInvariant(obj), false);
|
||||
|
||||
return Decimal.TryParse(escapedValue, out _) ? $"{escapedValue}" : $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean IsNonEmptyJsonArrayOrObject(String serialized) {
|
||||
if(serialized == EmptyObjectLiteral || serialized == 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
|
||||
global::System.Collections.Generic.Dictionary<global::System.String, global::System.Object?> objectDictionary = new Dictionary<global::System.String, global::System.Object?>();
|
||||
|
||||
if(String.IsNullOrWhiteSpace(this._options?.TypeSpecifier) == false) {
|
||||
objectDictionary[this._options?.TypeSpecifier!] = targetType;
|
||||
}
|
||||
|
||||
foreach(global::System.Collections.Generic.KeyValuePair<global::System.String, global::System.Reflection.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((Boolean)(this._options?.IncludeNonPublic)!)?.Invoke(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
|
||||
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();
|
||||
|
||||
if(targetType.IsEnum) {
|
||||
return Convert.ToInt64(target, System.Globalization.CultureInfo.InvariantCulture).ToString();
|
||||
}
|
||||
|
||||
global::System.Collections.Generic.Dictionary<global::System.String, global::System.Reflection.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
|
||||
global::System.Collections.Generic.Dictionary<global::System.String, global::System.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
|
||||
global::System.Collections.Generic.IEnumerable<global::System.Object> items = target.Cast<global::System.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Swan.Reflection;
|
||||
|
||||
namespace 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 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>>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SerializerOptions"/> class.
|
||||
/// </summary>
|
||||
/// <param name="format">if set to <c>true</c> [format].</param>
|
||||
/// <param name="typeSpecifier">The type specifier.</param>
|
||||
/// <param name="includeProperties">The include properties.</param>
|
||||
/// <param name="excludeProperties">The exclude properties.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
|
||||
/// <param name="parentReferences">The parent references.</param>
|
||||
/// <param name="jsonSerializerCase">The json serializer case.</param>
|
||||
public SerializerOptions(Boolean format, String? typeSpecifier, String[]? includeProperties, String[]? excludeProperties = null, Boolean includeNonPublic = true, IReadOnlyCollection<WeakReference>? parentReferences = null, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) {
|
||||
this._includeProperties = includeProperties;
|
||||
this._excludeProperties = excludeProperties;
|
||||
|
||||
this.IncludeNonPublic = includeNonPublic;
|
||||
this.Format = format;
|
||||
this.TypeSpecifier = typeSpecifier;
|
||||
this.JsonSerializerCase = jsonSerializerCase;
|
||||
|
||||
if(parentReferences == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(WeakReference parentReference in parentReferences.Where(x => x.IsAlive)) {
|
||||
_ = this.IsObjectPresent(parentReference.Target);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="SerializerOptions"/> is format.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if format; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean Format {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type specifier.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type specifier.
|
||||
/// </value>
|
||||
public String? TypeSpecifier {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether [include non public].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [include non public]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IncludeNonPublic {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer case.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The json serializer case.
|
||||
/// </value>
|
||||
public JsonSerializerCase JsonSerializerCase {
|
||||
get;
|
||||
}
|
||||
|
||||
internal Boolean IsObjectPresent(Object? target) {
|
||||
if(target == null) {
|
||||
return false;
|
||||
}
|
||||
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) => this.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 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.DefaultCache.Value.RetrieveAllProperties(targetType).Where(p => p.CanRead));
|
||||
|
||||
// If the target is a struct (value type) navigate the fields.
|
||||
if(targetType.IsValueType) {
|
||||
fields.AddRange(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType));
|
||||
}
|
||||
|
||||
Dictionary<Tuple<String, String>, MemberInfo> value = fields.ToDictionary(x => Tuple.Create(x.Name, x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name.GetNameWithCase(this.JsonSerializerCase)), x => x);
|
||||
|
||||
TypeCache.TryAdd(targetType, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Swan.Collections;
|
||||
using Swan.Reflection;
|
||||
|
||||
namespace 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 CollectionCacheRepository<String> IgnoredPropertiesCache = new CollectionCacheRepository<global::System.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 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 Swan.Attributes;
|
||||
/// using 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, params String[] excludedNames) => Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null, JsonSerializerCase.None);
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object into a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="jsonSerializerCase">The json serializer case.</param>
|
||||
/// <param name="format">if set to <c>true</c> [format].</param>
|
||||
/// <param name="typeSpecifier">The type specifier.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents the current object.
|
||||
/// </returns>
|
||||
public static String Serialize(Object? obj, JsonSerializerCase jsonSerializerCase, Boolean format = false, String? typeSpecifier = null) => Serialize(obj, format, typeSpecifier, false, null, null, null, jsonSerializerCase);
|
||||
|
||||
/// <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>
|
||||
/// <param name="jsonSerializerCase">The json serializer case.</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, JsonSerializerCase jsonSerializerCase) {
|
||||
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, jsonSerializerCase);
|
||||
|
||||
return Serialize(obj, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified object using the SerializerOptions provided.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="String" /> that represents the current object.
|
||||
/// </returns>
|
||||
public static String Serialize(Object? obj, SerializerOptions options) => 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="String" /> that represents the current object.</returns>
|
||||
/// <example>
|
||||
/// The following example shows how to serialize a simple object including the specified properties.
|
||||
/// <code>
|
||||
/// using 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) => Serialize(obj, new SerializerOptions(format, null, includeNames));
|
||||
|
||||
/// <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="String" /> that represents the current object.</returns>
|
||||
/// <example>
|
||||
/// The following code shows how to serialize a simple object excluding the specified properties.
|
||||
/// <code>
|
||||
/// using 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) => Serialize(obj, new SerializerOptions(format, null, null, excludeNames));
|
||||
|
||||
/// <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 string.</param>
|
||||
/// <param name="jsonSerializerCase">The json serializer case.</param>
|
||||
/// <returns>
|
||||
/// Type of the current deserializes.
|
||||
/// </returns>
|
||||
/// <example>
|
||||
/// The following code shows how to deserialize a JSON string into a Dictionary.
|
||||
/// <code>
|
||||
/// using Swan.Formatters;
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json to deserialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
/// // deserializes the specified json into a Dictionary<string, object>.
|
||||
/// var data = Json.Deserialize(basicJson, JsonSerializerCase.None);
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
public static Object? Deserialize(String? json, JsonSerializerCase jsonSerializerCase) => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase);
|
||||
|
||||
/// <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 string.</param>
|
||||
/// <returns>
|
||||
/// Type of the current deserializes.
|
||||
/// </returns>
|
||||
/// <example>
|
||||
/// The following code shows how to deserialize a JSON string into a Dictionary.
|
||||
/// <code>
|
||||
/// using Swan.Formatters;
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json to deserialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
/// // deserializes the specified json into a Dictionary<string, object>.
|
||||
/// var data = Json.Deserialize(basicJson);
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
public static Object? Deserialize(String? json) => Deserialize(json, JsonSerializerCase.None);
|
||||
|
||||
/// <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 string.</param>
|
||||
/// <param name="jsonSerializerCase">The JSON serializer case.</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 Swan.Formatters;
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // json type BasicJson to serialize
|
||||
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
|
||||
/// // deserializes the specified string in a new instance of the type BasicJson.
|
||||
/// var data = Json.Deserialize<BasicJson>(basicJson);
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
public static T Deserialize<T>(String json, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) where T : notnull => (T)Deserialize(json, typeof(T), jsonSerializerCase: jsonSerializerCase)!;
|
||||
|
||||
/// <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 string.</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) where T : notnull => (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 string.</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>
|
||||
/// <param name="jsonSerializerCase">The json serializer case.</param>
|
||||
/// <returns>
|
||||
/// Type of the current conversion from json result.
|
||||
/// </returns>
|
||||
public static Object? Deserialize(String json, Type resultType, Boolean includeNonPublic = false, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase, resultType, includeNonPublic);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private API
|
||||
|
||||
private static String[]? GetExcludedNames(Type? type, String[]? excludedNames) {
|
||||
if(type == null) {
|
||||
return excludedNames;
|
||||
}
|
||||
|
||||
global::System.Collections.Generic.IEnumerable<global::System.String> excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
|
||||
.Where(x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true)
|
||||
.Select(x => x.Name));
|
||||
|
||||
if(excludedByAttr?.Any() != true) {
|
||||
return excludedNames;
|
||||
}
|
||||
|
||||
return excludedNames?.Any(String.IsNullOrWhiteSpace) == true
|
||||
? excludedByAttr.Intersect(excludedNames.Where(y => !String.IsNullOrWhiteSpace(y))).ToArray()
|
||||
: excludedByAttr.ToArray();
|
||||
}
|
||||
|
||||
private static String SerializePrimitiveValue(Object obj) => obj switch
|
||||
{
|
||||
String stringValue => stringValue,
|
||||
Boolean boolValue => boolValue ? TrueLiteral : FalseLiteral,
|
||||
_ => obj.ToString()!
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace Swan.Formatters {
|
||||
/// <summary>
|
||||
/// An attribute used to help setup a property behavior when serialize/deserialize JSON.
|
||||
/// </summary>
|
||||
/// <seealso cref="Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class JsonPropertyAttribute : Attribute {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
/// <param name="ignored">if set to <c>true</c> [ignored].</param>
|
||||
public JsonPropertyAttribute(String propertyName, Boolean ignored = false) {
|
||||
this.PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
|
||||
this.Ignored = ignored;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the property.
|
||||
/// </value>
|
||||
public String PropertyName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if ignored; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean Ignored {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user