Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using System.Diagnostics;
using Newtonsoft.Json;
using System.Text.Json;
using Plugin.FirebasePushNotifications.Extensions;

namespace Plugin.FirebasePushNotifications
{
public class FirebasePushNotificationPreferences : IFirebasePushNotificationPreferences
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};

private readonly IPreferences preferences;

public FirebasePushNotificationPreferences(IPreferences preferences)
Expand All @@ -29,7 +34,7 @@ public void Set<T>(string key, T value)

if (value is not string serializedValue)
{
serializedValue = JsonConvert.SerializeObject(value);
serializedValue = JsonSerializer.Serialize(value, JsonSerializerOptions);
}

this.preferences.Set(key, serializedValue);
Expand Down Expand Up @@ -60,7 +65,7 @@ public T Get<T>(string key, T defaultValue = default)
}
else
{
value = JsonConvert.DeserializeObject<T>(serializedValue);
value = JsonSerializer.Deserialize<T>(serializedValue, JsonSerializerOptions);
}
}
catch (Exception ex)
Expand Down Expand Up @@ -114,4 +119,4 @@ public void ClearAll()
}
}
}
}
}
254 changes: 144 additions & 110 deletions Plugin.FirebasePushNotifications/Internals/DictionaryJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace Plugin.FirebasePushNotifications.Internals
{
Expand All @@ -11,12 +11,11 @@ namespace Plugin.FirebasePushNotifications.Internals
/// </summary>
public static class DictionaryJsonConverter
{
private static readonly JsonSerializer DefaultJsonSerializer = new JsonSerializer()
private static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new JsonSerializerOptions
{
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
NullValueHandling = NullValueHandling.Ignore,
Converters = { new IsoDateTimeConverter() }
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};

/// <summary>
Expand All @@ -26,41 +25,36 @@ public static class DictionaryJsonConverter
/// <returns>Flat dictionary with path as key and value as string.</returns>
public static IDictionary<string, string> Flatten<T>(T source)
{
return Flatten(source, DefaultJsonSerializer);
return Flatten(source, DefaultJsonSerializerOptions);
}

/// <summary>
/// Creates a flat dictionary for <paramref name="source"/> of type <typeparamref name="T"/>.
/// </summary>
/// <param name="source">Source object.</param>
/// <param name="jsonSerializer">Custom JsonSerializer used to serialize <paramref name="source"/>.</param>
/// <param name="jsonSerializerOptions">Custom options used to serialize <paramref name="source"/>.</param>
/// <returns>Flat dictionary with path as key and value as string.</returns>
public static IDictionary<string, string> Flatten<T>(T source, JsonSerializer jsonSerializer)
public static IDictionary<string, string> Flatten<T>(T source, JsonSerializerOptions jsonSerializerOptions)
{
var jObject = JObject.FromObject(source, jsonSerializer);
return Flatten(jObject);
var jsonObject = JsonSerializer.SerializeToNode(source, jsonSerializerOptions) as JsonObject ?? new JsonObject();
return Flatten(jsonObject);
}

/// <summary>
/// Creates a flat dictionary for <paramref name="source"/> of type <seealso cref="JObject"/>.
/// Creates a flat dictionary for <paramref name="source"/> of type <seealso cref="JsonObject"/>.
/// </summary>
/// <param name="source">Source object.</param>
/// <returns>Flat dictionary with path as key and value as string.</returns>
public static IDictionary<string, string> Flatten(JObject source)
public static IDictionary<string, string> Flatten(JsonObject source)
{
var jTokens = source
.Descendants()
.Where(p => !p.Any());
var results = new Dictionary<string, string>();

var results = jTokens
.Select(jToken =>
{
var value = (jToken as JValue)?.Value?.ToString();
var key = jToken.Path;
return new KeyValuePair<string, string>(key, value);
})
.ToDictionary(x => x.Key, x => x.Value);
if (source == null)
{
return results;
}

FlattenInternal(source, null, results);
return results;
}

Expand All @@ -72,102 +66,45 @@ public static IDictionary<string, string> Flatten(JObject source)
/// <returns>Target object.</returns>
public static T Unflatten<T>(IDictionary<string, string> dictionary)
{
var jObject = Unflatten(dictionary);
return jObject.ToObject<T>(DefaultJsonSerializer);
var jsonObject = Unflatten(dictionary);
return jsonObject.Deserialize<T>(DefaultJsonSerializerOptions);
}

/// <inheritdoc cref="Unflatten{T}(IDictionary{string, string})"/>
/// <param name="dictionary">The source dictionary.</param>
/// <param name="jsonSerializer">A custom json serializer.</param>
public static T Unflatten<T>(IDictionary<string, string> dictionary, JsonSerializer jsonSerializer)
/// <param name="jsonSerializerOptions">A custom json serializer options instance.</param>
public static T Unflatten<T>(IDictionary<string, string> dictionary, JsonSerializerOptions jsonSerializerOptions)
{
return (T)Unflatten(dictionary, typeof(T), jsonSerializer);
return (T)Unflatten(dictionary, typeof(T), jsonSerializerOptions);
}

/// <summary>
/// Unflattens a source <paramref name="dictionary"/> into an object of type <paramref name="targetType"/>.
/// </summary>
/// <param name="dictionary">The source dictionary.</param>
/// <param name="targetType">The target type.</param>
/// <param name="jsonSerializer">A custom json serializer.</param>
public static object Unflatten(IDictionary<string, string> dictionary, Type targetType, JsonSerializer jsonSerializer)
/// <param name="jsonSerializerOptions">A custom json serializer options instance.</param>
public static object Unflatten(IDictionary<string, string> dictionary, Type targetType, JsonSerializerOptions jsonSerializerOptions)
{
var jObject = Unflatten(dictionary);
return jObject.ToObject(targetType, jsonSerializer);
var jsonObject = Unflatten(dictionary);
return jsonObject.Deserialize(targetType, jsonSerializerOptions);
}

/// <summary>
/// Creates a <see cref="JObject"/> from a given flat <paramref name="dictionary"/>.
/// Creates a <see cref="JsonObject"/> from a given flat <paramref name="dictionary"/>.
/// </summary>
/// <param name="dictionary">The source dictionary.</param>
/// <returns>The structured result.</returns>
public static JObject Unflatten(IDictionary<string, string> dictionary)
public static JsonObject Unflatten(IDictionary<string, string> dictionary)
{
JContainer result = null;
var setting = new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Merge };
foreach (var pathValue in dictionary)
{
if (result == null)
{
result = UnflattenSingle(pathValue);
}
else
{
result.Merge(UnflattenSingle(pathValue), setting);
}
}
var root = new JsonObject();

return result as JObject;
}

private static JContainer UnflattenSingle(KeyValuePair<string, string> keyValue)
{
var path = keyValue.Key;
var value = keyValue.Value;
var pathSegments = SplitPath(path);

JContainer lastItem = null;

// Build from leaf to root
foreach (var pathSegment in pathSegments.Reverse())
foreach (var pathValue in dictionary)
{
var type = GetJsonTokenType(pathSegment);
switch (type)
{
case JTokenType.Object:
var obj = new JObject();
if (null == lastItem)
{
obj.Add(pathSegment, value);
}
else
{
obj.Add(pathSegment, lastItem);
}

lastItem = obj;
break;
case JTokenType.Array:
var array = new JArray();
var index = GetArrayIndex(pathSegment);
array = FillEmpty(array, index);
if (lastItem == null)
{
array[index] = value;
}
else
{
array[index] = lastItem;
}

lastItem = array;
break;
default:
throw new NotSupportedException($"UnflattenSingle does not support type {type}.");
}
InsertPathValue(root, pathValue.Key, pathValue.Value);
}

return lastItem;
return root;
}

public static IEnumerable<string> SplitPath(string path)
Expand All @@ -179,29 +116,126 @@ public static IEnumerable<string> SplitPath(string path)
}
}

private static JArray FillEmpty(JArray array, int index)
private static void FlattenInternal(JsonNode node, string currentPath, IDictionary<string, string> results)
{
for (var i = 0; i <= index; i++)
switch (node)
{
array.Add(null);
case JsonObject jsonObject:
foreach (var property in jsonObject)
{
var nextPath = string.IsNullOrEmpty(currentPath)
? property.Key
: $"{currentPath}.{property.Key}";

FlattenInternal(property.Value, nextPath, results);
}

break;

case JsonArray jsonArray:
for (var i = 0; i < jsonArray.Count; i++)
{
var nextPath = $"{currentPath}[{i}]";
FlattenInternal(jsonArray[i], nextPath, results);
}

break;

case JsonValue jsonValue:
results[currentPath] = GetJsonValueAsString(jsonValue);
break;

case null:
results[currentPath] = null;
break;
}
}

return array;
private static string GetElementStringValue(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Null => null,
_ => element.ToString(),
};
}

private static JTokenType GetJsonTokenType(string pathSegment)
private static string GetJsonValueAsString(JsonValue jsonValue)
{
return int.TryParse(pathSegment, out _) ? JTokenType.Array : JTokenType.Object;
if (jsonValue.TryGetValue<string>(out var stringValue))
{
return stringValue;
}

if (jsonValue.TryGetValue<JsonElement>(out var jsonElement))
{
return GetElementStringValue(jsonElement);
}

return jsonValue.ToJsonString().Trim('"');
}

private static int GetArrayIndex(string pathSegment)
private static void InsertPathValue(JsonObject root, string path, string value)
{
if (int.TryParse(pathSegment, out var result))
var pathSegments = SplitPath(path).ToArray();
JsonNode current = root;

for (var i = 0; i < pathSegments.Length; i++)
{
return result;
var segment = pathSegments[i];
var isLast = i == pathSegments.Length - 1;
var isArraySegment = int.TryParse(segment, out var arrayIndex);

if (isArraySegment)
{
var array = current as JsonArray;
if (array == null)
{
throw new NotSupportedException($"Path segment '{segment}' is an array index but current node is not an array.");
}

EnsureArraySize(array, arrayIndex);

if (isLast)
{
array[arrayIndex] = JsonValue.Create(value);
}
else
{
var nextSegmentIsArray = int.TryParse(pathSegments[i + 1], out _);
array[arrayIndex] ??= nextSegmentIsArray ? new JsonArray() : new JsonObject();
current = array[arrayIndex];
}
}
else
{
var obj = current as JsonObject;
if (obj == null)
{
throw new NotSupportedException($"Path segment '{segment}' is an object key but current node is not an object.");
}

if (isLast)
{
obj[segment] = JsonValue.Create(value);
}
else
{
var nextSegmentIsArray = int.TryParse(pathSegments[i + 1], out _);
obj[segment] ??= nextSegmentIsArray ? new JsonArray() : new JsonObject();
current = obj[segment];
}
}
}
}

throw new Exception("Unable to parse array index: " + pathSegment);
private static void EnsureArraySize(JsonArray array, int index)
{
while (array.Count <= index)
{
array.Add(null);
}
}
}
}
}
Loading