Skip to content

Commit

Permalink
dynamic serializer, ignore comment improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
CypherPotato committed Aug 23, 2024
1 parent 37e8149 commit d3b6b89
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Sources/LightJson/Converters/DictionaryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public override object Deserialize(JsonValue value, Type requestedType)
{
if (requestedType == typeof(ExpandoObject))
{
return JsonValueToObject(value, 0) !;
return JsonValueToObject(value, 0)!;
}
else
{
Expand Down
1 change: 0 additions & 1 deletion Sources/LightJson/Converters/TupleConverter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace LightJson.Converters;
Expand Down
143 changes: 140 additions & 3 deletions Sources/LightJson/Dynamic.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,141 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;

namespace LightJson;

internal class Dynamic
{
public static object DeserializeObject(JsonValue value, Type tinputType, JsonOptions options)
{
string SanitizeCharcase(string s)
{
return new string(s.Where(char.IsLetterOrDigit).ToArray());
}

bool ICharcaseCompare(string a, string b)
{
string A = SanitizeCharcase(a);
string B = SanitizeCharcase(b);
return string.Compare(A, B, true) == 0;
}

object DeserializeObjectX(JsonValue value, Type type, int deepness, JsonOptions options)
{
if (type == typeof(int) || type == typeof(uint) ||
type == typeof(long) || type == typeof(ulong) ||
type == typeof(double) || type == typeof(float) ||
type == typeof(byte) || type == typeof(sbyte))
{
return Convert.ChangeType(value.GetNumber(), type);
}
else if (type == typeof(string))
{
return value.GetString();
}
else if (type == typeof(bool))
{
return value.GetBoolean();
}
else if (type == typeof(JsonObject))
{
return value.GetJsonObject();
}
else if (type == typeof(JsonArray))
{
return value.GetJsonArray();
}
else
{
for (int i = 0; i < options.Converters.Count; i++)
{
var mapper = options.Converters[i];
if (mapper.CanSerialize(type))
{
try
{
return mapper.Deserialize(value, type);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Caught exception while trying to map {value.path} to {type.Name}: {ex.Message}");
}
}
}

if (type.IsAssignableTo(typeof(IEnumerable)))
{
Type subType = type.GetGenericArguments()[0];
var arr = value.GetJsonArray();

ArrayList items = new ArrayList();
for (int i = 0; i < arr.Count; i++)
{
items.Add(DeserializeObjectX(value, subType, deepness + 1, options));
}

if (type.IsAssignableTo(typeof(ICollection<>)))
{
return items.ToArray().ToList();
}
else
{
return items.ToArray();
}
}
else
{
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var jobj = value.GetJsonObject();

object? objInstance = Activator.CreateInstance(type);
if (objInstance is null)
{
throw new InvalidOperationException($"The JSON deserializer couldn't create an instance of {type.Name}.");
}

for (int i = 0; i < properties.Length; i++)
{
var prop = properties[i];

string propName = prop.Name;
JsonValue? jobjChild = jobj
.Properties
.Where(p => ICharcaseCompare(propName, p.Key))
.Select(p => p.Value)
.Cast<JsonValue?>()
.FirstOrDefault();

if (jobjChild is JsonValue jvalue)
{
if (prop.GetCustomAttribute<JsonIgnoreAttribute>() is JsonIgnoreAttribute ignore)
{
if (ignore.Condition == JsonIgnoreCondition.WhenWritingDefault && jvalue.IsNull)
{
continue;
}
else if (ignore.Condition == JsonIgnoreCondition.Always)
{
continue;
}
}

object? jsonValueObj = jvalue.MaybeNull()?.Get(prop.PropertyType);
prop.SetValue(objInstance, jsonValueObj);
}
}

return objInstance;
}
}
}

return DeserializeObjectX(value, tinputType, 0, options);
}

public static JsonValue SerializeObject(object? value, int deepness, bool convertersEnabled, JsonOptions options, out JsonValueType valueType)
{
if (deepness > options.DynamicObjectMaxDepth)
Expand Down Expand Up @@ -54,8 +183,9 @@ public static JsonValue SerializeObject(object? value, int deepness, bool conver

if (convertersEnabled)
{
foreach (var mapper in options.Converters)
for (int i = 0; i < options.Converters.Count; i++)
{
Converters.JsonConverter? mapper = options.Converters[i];
if (mapper.CanSerialize(itemType))
{
var result = mapper.Serialize(value);
Expand All @@ -65,6 +195,11 @@ public static JsonValue SerializeObject(object? value, int deepness, bool conver
}
}

if (!options.DynamicSerialization.HasFlag(DynamicSerializationMode.Write))
{
throw new InvalidOperationException($"No converter matched the object type {itemType.FullName}.");
}

if (itemType.IsAssignableTo(typeof(IEnumerable)))
{
JsonArray arr = new JsonArray(options);
Expand All @@ -83,8 +218,9 @@ public static JsonValue SerializeObject(object? value, int deepness, bool conver
PropertyInfo[] valueProperties = itemType
.GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (PropertyInfo property in valueProperties)
for (int i = 0; i < valueProperties.Length; i++)
{
PropertyInfo property = valueProperties[i];
var atrJsonIgnore = property.GetCustomAttribute<JsonIgnoreAttribute>();
var atrJsonProperty = property.GetCustomAttribute<JsonPropertyNameAttribute>();

Expand All @@ -108,8 +244,9 @@ public static JsonValue SerializeObject(object? value, int deepness, bool conver
FieldInfo[] fields = itemType
.GetFields(BindingFlags.Public | BindingFlags.Instance);

foreach (FieldInfo field in fields)
for (int i = 0; i < fields.Length; i++)
{
FieldInfo field = fields[i];
var atrJsonIgnore = field.GetCustomAttribute<JsonIgnoreAttribute>();
var atrJsonProperty = field.GetCustomAttribute<JsonPropertyNameAttribute>();

Expand Down
31 changes: 31 additions & 0 deletions Sources/LightJson/JsonArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,37 @@ public bool Remove(JsonValue item)
return items.Remove(item);
}

/// <summary>
/// Casts every <see cref="JsonValue"/> in this array into an <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type to cast the JsonValue into.</typeparam>
public IEnumerable<T> EveryAs<T>() where T : notnull
{
foreach (var jsonitem in items)
{
yield return jsonitem.Get<T>();
}
}

/// <summary>
/// Casts every <see cref="JsonValue"/> in this array into an <typeparamref name="T"/>.
/// This method also includes null values.
/// </summary>
/// <typeparam name="T">The type to cast the JsonValue into.</typeparam>
public IEnumerable<T?> EveryAsNullable<T>() where T : notnull
{
foreach (var jsonitem in items)
{
if (jsonitem.IsNull)
{
yield return default;
}
else
{
yield return jsonitem.Get<T>();
}
}
}

/// <summary>
/// Returns a JSON string representing the state of this value.
Expand Down
43 changes: 34 additions & 9 deletions Sources/LightJson/JsonConverterCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace LightJson;
/// <summary>
/// Represents an collection of <see cref="JsonConverter"/>.
/// </summary>
public class JsonConverterCollection : ICollection<JsonConverter>
public class JsonConverterCollection : IList<JsonConverter>
{
private List<JsonConverter> _converters;
private JsonOptions parent;
Expand All @@ -24,10 +24,17 @@ public JsonConverterCollection(JsonOptions parent)
}

/// <inheritdoc/>
public int Count => ((ICollection<JsonConverter>)_converters).Count;
public JsonConverter this[int index]
{
get => _converters[index];
set => _converters[index] = value;
}

/// <inheritdoc/>
public int Count => _converters.Count;

/// <inheritdoc/>
public bool IsReadOnly => ((ICollection<JsonConverter>)_converters).IsReadOnly;
public bool IsReadOnly => false;

/// <inheritdoc/>
public void Add(JsonConverter item)
Expand All @@ -49,36 +56,54 @@ public void AddRange(IEnumerable<JsonConverter> items)
/// <inheritdoc/>
public void Clear()
{
((ICollection<JsonConverter>)_converters).Clear();
_converters.Clear();
}

/// <inheritdoc/>
public bool Contains(JsonConverter item)
{
return ((ICollection<JsonConverter>)_converters).Contains(item);
return _converters.Contains(item);
}

/// <inheritdoc/>
public void CopyTo(JsonConverter[] array, int arrayIndex)
{
((ICollection<JsonConverter>)_converters).CopyTo(array, arrayIndex);
_converters.CopyTo(array, arrayIndex);
}

/// <inheritdoc/>
public IEnumerator<JsonConverter> GetEnumerator()
{
return ((IEnumerable<JsonConverter>)_converters).GetEnumerator();
return _converters.GetEnumerator();
}

/// <inheritdoc/>
public int IndexOf(JsonConverter item)
{
return _converters.IndexOf(item);
}

/// <inheritdoc/>
public void Insert(int index, JsonConverter item)
{
_converters.Insert(index, item);
}

/// <inheritdoc/>
public bool Remove(JsonConverter item)
{
return ((ICollection<JsonConverter>)_converters).Remove(item);
return _converters.Remove(item);
}

/// <inheritdoc/>
public void RemoveAt(int index)
{
_converters.RemoveAt(index);
}

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_converters).GetEnumerator();
return GetEnumerator();
}
}
29 changes: 28 additions & 1 deletion Sources/LightJson/JsonOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using LightJson.Converters;
using LightJson.Serialization;
using System.Diagnostics.CodeAnalysis;
using System;
using System.Text.Json;

namespace LightJson;
Expand Down Expand Up @@ -62,6 +62,11 @@ public class JsonOptions
/// </summary>
public int DynamicObjectMaxDepth { get; set; } = 64;

/// <summary>
/// Gets or sets the <see cref="DynamicSerializationMode"/> used by the JSON serializer.
/// </summary>
public DynamicSerializationMode DynamicSerialization { get; set; } = DynamicSerializationMode.Both;

/// <summary>
/// Creates an new <see cref="JsonOptions"/> instance with default parameters.
/// </summary>
Expand Down Expand Up @@ -94,3 +99,25 @@ public JsonOptions()
/// </summary>
public JsonArray CreateJsonArray() => new JsonArray(this);
}

/// <summary>
/// Represents an JSON dynamic serialization mode.
/// </summary>
[Flags]
public enum DynamicSerializationMode
{
/// <summary>
/// Represents that the JSON serializer can write dynamic JSON for non-mapped objects.
/// </summary>
Write,

/// <summary>
/// Represents that the JSON serializer can read dynamic JSON for non-mapped objects.
/// </summary>
Read,

/// <summary>
/// Represents that the JSON serializer can read and write dynamic JSON for non-mapped objects.
/// </summary>
Both = Read | Write
}
Loading

0 comments on commit d3b6b89

Please sign in to comment.