Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ce49354
Imprort YAML bundles via converting them first to JSON
marticliment May 18, 2025
c8f67ef
XML will also be converted to JSON prior to deserialization
marticliment May 18, 2025
953ad48
Only use special data converters when handling bundles. Otherwhise us…
marticliment May 18, 2025
7f14a27
fix serialization tests not passing
marticliment May 18, 2025
8c6d8a8
Load installation options as a dynamic JSON, instead of using deseria…
marticliment May 18, 2025
3fbb1ce
add missing line to InstallationOptions.ToString()
marticliment May 18, 2025
579bcd0
Create SerializableComponent: Better, unified API for serializable ob…
marticliment May 20, 2025
393ce8a
Add tests for SerializableInstallationOptions
marticliment May 20, 2025
926c679
Add more tests to SerializableInstallationOptions, ensure proper JSON…
marticliment May 20, 2025
16698c5
Add tests to SerializableIncompatiblePackage
marticliment May 20, 2025
52d3522
Migrate SerializableUpdatesOptions_v1
marticliment May 20, 2025
04cf96f
Migrate SerializablePackage
marticliment May 20, 2025
84b7c36
Better tests for SerializablePackage
marticliment May 20, 2025
732d9a7
Migrate SerializableBundle
marticliment May 20, 2025
1b773c5
Fix YAML and XML serialization, remove now useless Converters
marticliment May 20, 2025
d1393d0
Merge branch 'main' into dynamic-json-{de}serialization
marticliment May 20, 2025
795a5cb
Add a method that checks if a SerializableInstallOptions differs or n…
marticliment May 21, 2025
af77401
add test for this
marticliment May 21, 2025
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,7 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">



<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
Expand Down
7 changes: 1 addition & 6 deletions src/UniGetUI.Core.Data/CoreData.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using UniGetUI.Core.Logging;

Expand Down Expand Up @@ -60,7 +61,7 @@
}
} else if (IS_PORTABLE is true)
{
return PORTABLE_PATH ?? throw new Exception("This shouldn't be possible");

Check warning on line 64 in src/UniGetUI.Core.Data/CoreData.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Exception type System.Exception is not sufficiently specific (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2201)

Check warning on line 64 in src/UniGetUI.Core.Data/CoreData.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Exception type System.Exception is not sufficiently specific (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2201)
}

string old_path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".wingetui");
Expand Down Expand Up @@ -357,12 +358,6 @@
}
}

public static JsonSerializerOptions SerializingOptions = new()
{
TypeInfoResolverChain = { new DefaultJsonTypeInfoResolver() },
WriteIndented = true,
};

private static int GetCodePage()
{
try
Expand Down
4 changes: 4 additions & 0 deletions src/UniGetUI.Core.Data/UniGetUI.Core.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@
<ItemGroup>
<Compile Include="..\SharedAssemblyInfo.cs" Link="SharedAssemblyInfo.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/UniGetUI.Core.IconStore/IconDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task LoadIconAndScreenshotsDatabaseAsync()
{
IconScreenshotDatabase_v2 JsonData = JsonSerializer.Deserialize<IconScreenshotDatabase_v2>(
await File.ReadAllTextAsync(IconsAndScreenshotsFile),
CoreData.SerializingOptions
SerializationHelpers.DefaultOptions
);
if (JsonData.icons_and_screenshots is not null)
{
Expand Down
6 changes: 3 additions & 3 deletions src/UniGetUI.Core.Settings.Tests/SettingsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
Assert.Equal("this is now a test case", Settings.GetListItem<string>(SettingName, 3));
Assert.Null(Settings.GetListItem<string>(SettingName, 4));

Assert.Equal(Settings.GetListItem<string>(SettingName, 0), JsonSerializer.Deserialize<List<string>>(File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")), CoreData.SerializingOptions)[0]);
Assert.Equal(Settings.GetListItem<string>(SettingName, 0), JsonSerializer.Deserialize<List<string>>(File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")), Settings.SerializationOptions)[0]);

Check warning on line 176 in src/UniGetUI.Core.Settings.Tests/SettingsTest.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Dereference of a possibly null reference.
Settings.ClearList(SettingName);
Assert.Empty(Settings.GetList<object>(SettingName) ?? ["this shouldn't be null; something's wrong"]);

Expand Down Expand Up @@ -226,7 +226,7 @@
Settings.SetDictionaryItem(randStr, "key", 12);
Assert.Equal(12, Settings.GetDictionaryItem<string, int>(randStr, "key"));
Settings.SetDictionary(SettingName, test);
Assert.Equal(JsonSerializer.Serialize(test, CoreData.SerializingOptions), File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")));
Assert.Equal(JsonSerializer.Serialize(test, Settings.SerializationOptions), File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")));
Assert.Equal(test[keyArray[0]]?.sub.count, Settings.GetDictionary<string, SerializableTest?>(SettingName)?[keyArray[0]]?.sub.count);
Assert.Equal(test[keyArray[1]]?.sub.count, Settings.GetDictionaryItem<string, SerializableTest?>(SettingName, keyArray[1])?.sub.count);
Settings.SetDictionaryItem(SettingName, keyArray[0], test[keyArray[1]]);
Expand Down Expand Up @@ -257,7 +257,7 @@
Assert.True(Settings.DictionaryContainsValue<string, SerializableTest?>(SettingName, test[keyArray[2]]));

Assert.Equal(
JsonSerializer.Serialize(Settings.GetDictionary<string, SerializableTest>(SettingName), CoreData.SerializingOptions),
JsonSerializer.Serialize(Settings.GetDictionary<string, SerializableTest>(SettingName), Settings.SerializationOptions),
File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json"))
);

Expand Down
4 changes: 2 additions & 2 deletions src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{
Logger.Error(
$"Tried to get a dictionary setting with a key of type {typeof(K)} and a value of type {typeof(V)}, which is not the type of the dictionary");
return null;

Check warning on line 33 in src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Possible null reference return.

Check warning on line 33 in src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Possible null reference return.
}

// Otherwise, load the setting from disk and cache that setting
Expand All @@ -43,7 +43,7 @@
if (result != "")
{

Dictionary<K, V?>? item = JsonSerializer.Deserialize<Dictionary<K, V?>>(result, CoreData.SerializingOptions);
Dictionary<K, V?>? item = JsonSerializer.Deserialize<Dictionary<K, V?>>(result, SerializationOptions);
if (item is not null)
{
value = item;
Expand Down Expand Up @@ -90,7 +90,7 @@
try
{

if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, CoreData.SerializingOptions));
if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationOptions));
else if (File.Exists(file)) File.Delete(file);
}
catch (Exception e)
Expand Down
9 changes: 9 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Windows.Security.Credentials;
using UniGetUI.Core.Logging;

Expand Down Expand Up @@ -83,4 +85,11 @@ public static void SetProxyCredentials(string username, string password)
Logger.Error(ex);
}
}

public static JsonSerializerOptions SerializationOptions = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
AllowTrailingCommas = true,
WriteIndented = true,
};
}
4 changes: 2 additions & 2 deletions src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static void ExportToJSON(string path)
settings.Add(Path.GetFileName(entry), File.ReadAllText(entry));
}

File.WriteAllText(path, JsonSerializer.Serialize(settings, CoreData.SerializingOptions));
File.WriteAllText(path, JsonSerializer.Serialize(settings, SerializationOptions));
}

public static void ImportFromJSON(string path)
Expand All @@ -31,7 +31,7 @@ public static void ImportFromJSON(string path)
}

ResetSettings();
Dictionary<string, string> settings = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(path), CoreData.SerializingOptions) ?? [];
Dictionary<string, string> settings = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(path), SerializationOptions) ?? [];
foreach (KeyValuePair<string, string> entry in settings)
{
if(new[] {"OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken"}.Contains(entry.Key))
Expand Down
4 changes: 2 additions & 2 deletions src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static partial class Settings
{
if (result != "")
{
List<T>? item = JsonSerializer.Deserialize<List<T>>(result, CoreData.SerializingOptions);
List<T>? item = JsonSerializer.Deserialize<List<T>>(result, SerializationOptions);
if (item is not null)
{
value = item;
Expand Down Expand Up @@ -76,7 +76,7 @@ public static void SetList<T>(string setting, List<T> value)
var file = Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{setting}.json");
try
{
if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, CoreData.SerializingOptions));
if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationOptions));
else if (File.Exists(file)) File.Delete(file);
}
catch (Exception e)
Expand Down
8 changes: 5 additions & 3 deletions src/UniGetUI.Core.Tools.Tests/MetaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ public void TestJsonSerializationOptions()
var lines = File.ReadAllLines(file);
var jsonSerCount = lines.Count(x => x.Contains("JsonSerializer.Serialize"));
var jsonDeserCount = lines.Count(x => x.Contains("JsonSerializer.Deserialize"));
var serialOptionsCount = lines.Count(x => x.Contains("CoreData.SerializingOptions"));
Assert.True((jsonSerCount + jsonDeserCount) <= serialOptionsCount,
var serialOptionsCount1 = lines.Count(x => x.Contains("SerializationHelpers.DefaultOptions"));
var serialOptionsCount2 = lines.Count(x => x.Contains("SerializationHelpers.ImportBundleOptions"));
var serialOptionsCount3 = lines.Count(x => x.Contains("SerializationOptions"));
Assert.True((jsonSerCount + jsonDeserCount) <= serialOptionsCount1 + serialOptionsCount2 + serialOptionsCount3,
$"Failing on {file}. The specified file does not serialize and/or deserialize JSON with" +
$" the proper CoreData.SerializingOptions set");
$" the proper CoreData.DefaultOptions set");
}
}

Expand Down
135 changes: 135 additions & 0 deletions src/UniGetUI.Core.Tools/SerializationHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Xml;

namespace UniGetUI.Core.Data;

public static class SerializationHelpers
{
public static Task<string> YAML_to_JSON(string YAML)
=> Task.Run(() => yaml_to_json(YAML));

private static string yaml_to_json(string YAML)
{
var yamlObject = new YamlDotNet.Serialization.Deserializer().Deserialize(YAML);
if (yamlObject is null) return "{'message': 'deserialized YAML object was null'}";
return new YamlDotNet.Serialization.SerializerBuilder()
.JsonCompatible()
.Build()
.Serialize(yamlObject);
}

public static Task<string> XML_to_JSON(string XML)
=> Task.Run(() => xml_to_json(XML));

private static string xml_to_json(string XML)
{
var doc = new XmlDocument();
doc.LoadXml(XML);
if (doc.DocumentElement is null) return "{'message': 'XmlDocument.DocumentElement was null'}";
return JsonSerializer.Serialize(_convertXmlNode(doc.DocumentElement), SerializationHelpers.DefaultOptions);
}

private static object? _convertXmlNode(XmlNode node)
{
// If node has no children, return its text or attributes
if (node.ChildNodes.Count == 1 && node.FirstChild is XmlText singleText)
{
return singleText.Value;
}

// Attributes dictionary
var dict = new Dictionary<string, object>();
if (node.Attributes?.Count > 0)
{
foreach (XmlAttribute attr in node.Attributes)
{
dict[$"@{attr.Name}"] = attr.Value;
}
}

// Group child elements
var children = new Dictionary<string, List<object>>();
foreach (XmlNode child in node.ChildNodes)
{
if (child is XmlElement childElement)
{
var value = _convertXmlNode(childElement);
if (!children.ContainsKey(childElement.Name))
children[childElement.Name] = new List<object>();
children[childElement.Name].Add(value);

Check warning on line 63 in src/UniGetUI.Core.Tools/SerializationHelpers.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 63 in src/UniGetUI.Core.Tools/SerializationHelpers.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.
}
}

// Flatten repeated elements if only one group exists
if (children.Count == 1 && dict.Count == 0)
{
var firstKey = children.Keys.First();
return children[firstKey].Count == 1 ? children[firstKey][0] : children[firstKey];
}

// Otherwise build normal object
foreach (var kv in children)
{
dict[kv.Key] = kv.Value.Count == 1 ? kv.Value[0] : kv.Value;
}

return dict;
}

public static JsonSerializerOptions DefaultOptions = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
AllowTrailingCommas = true,
WriteIndented = true,
};
}

public static class JsonNodeExtensions
{
/// <summary>
/// Safely gets a child node by key, returning null if the key does not exist.
/// </summary>
public static T GetVal<T>(this JsonNode node)
{
if (typeof(T) == typeof(double) && node.GetValueKind() is JsonValueKind.String)
return (T)(object)double.Parse(node.GetValue<string>(), CultureInfo.InvariantCulture);

if (typeof(T) == typeof(int) && node.GetValueKind() is JsonValueKind.String)
return (T)(object)int.Parse(node.GetValue<string>());

if (typeof(T) == typeof(bool) && node.GetValueKind() is JsonValueKind.String)
return (T)(object)bool.Parse(node.GetValue<string>());

if (typeof(T) == typeof(string) && node.GetValueKind() is JsonValueKind.Object)
{
return (T)(object)"";
}

return node.GetValue<T>();
}

/// <summary>
/// The same as JsonNode.AsArray, but can convert objects whose keys are integers to arrays
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public static JsonArray AsArray2(this JsonNode node)
{
if (node is JsonValue val)
{
JsonArray result = new();
result.Add(val.DeepClone());
return result;
}
else if (node is JsonObject obj && !obj.Any())
{
return new JsonArray();
}

return node.AsArray();
}
}
8 changes: 4 additions & 4 deletions src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ public interface IInstallationOptions
public IPackage Package { get; }

/// <summary>
/// Loads and applies the options from the given SerializableInstallationOptions_v1 object to the current object.
/// Loads and applies the options from the given SerializableInstallationOptions object to the current object.
/// </summary>
public void FromSerializable(SerializableInstallationOptions_v1 options);
public void FromSerializable(SerializableInstallationOptions options);

/// <summary>
/// Returns a SerializableInstallationOptions_v1 object containing the options of the current instance.
/// Returns a SerializableInstallationOptions object containing the options of the current instance.
/// </summary>
public SerializableInstallationOptions_v1 AsSerializable();
public SerializableInstallationOptions AsSerializable();

/// <summary>
/// Saves the current options to disk, asynchronously.
Expand Down
4 changes: 2 additions & 2 deletions src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ public interface IPackage : INotifyPropertyChanged, IEquatable<IPackage>
/// <returns>False if the update is a major update or the update doesn't exist, true if it's a minor update</returns>
public bool IsUpdateMinor();

public SerializablePackage_v1 AsSerializable();
public SerializablePackage AsSerializable();

public SerializableIncompatiblePackage_v1 AsSerializable_Incompatible();
public SerializableIncompatiblePackage AsSerializable_Incompatible();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private static T Fetch<T>(Uri url)

var manifestStr = client.GetStringAsync(url).GetAwaiter().GetResult();

var manifest = JsonSerializer.Deserialize<T>(manifestStr, options: CoreData.SerializingOptions)
var manifest = JsonSerializer.Deserialize<T>(manifestStr, options: SerializationHelpers.DefaultOptions)
?? throw new NullResponseException($"Null response for request to {url}");
return manifest;
}
Expand Down
Loading
Loading