Skip to content

Commit

Permalink
Switched from Newtonsoft.Json to System.Text.Json (some very hacky ch…
Browse files Browse the repository at this point in the history
…anges were required). Updated dependencies.
  • Loading branch information
natekford committed Jan 2, 2025
1 parent 338dfaa commit bcd323c
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 149 deletions.
2 changes: 1 addition & 1 deletion src/SongProcessor.UI/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public override void OnFrameworkInitializationCompleted()

// Set up suspension to save view model information
var suspension = new AutoSuspendHelper(ApplicationLifetime!);
var driver = new NewtonsoftJsonSuspensionDriver("appstate.json")
var driver = new JsonSuspensionDriver("appstate.json")
{
#if DEBUG
DeleteOnInvalidState = false,
Expand Down
116 changes: 116 additions & 0 deletions src/SongProcessor.UI/JsonSuspensionDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using ReactiveUI;

using SongProcessor.UI.ViewModels;

using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace SongProcessor.UI;

public class JsonSuspensionDriver(string Path) : ISuspensionDriver
{
private static readonly JsonSerializerOptions _Options = new()
{
IgnoreReadOnlyFields = true,
IgnoreReadOnlyProperties = true,
WriteIndented = true,
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers =
{
UseDataContract,
UseTypeNamesForViewModels,
IgnoreCertainViewModels,
}
}
};
public bool DeleteOnInvalidState { get; set; }

public IObservable<Unit> InvalidateState()
{
if (DeleteOnInvalidState && File.Exists(Path))
{
File.Delete(Path);
}
return Observable.Return(Unit.Default);
}

public IObservable<object> LoadState()
{
// ReactiveUI relies on this method throwing an exception
// to determine if CreateNewAppState should be called
using var fs = File.OpenRead(Path);
var state = JsonSerializer.Deserialize<MainViewModel>(fs, _Options);
return Observable.Return(state)!;
}

public IObservable<Unit> SaveState(object state)
{
using var fs = File.Create(Path);
JsonSerializer.Serialize(fs, state, _Options);
return Observable.Return(Unit.Default);
}

private static void IgnoreCertainViewModels(JsonTypeInfo typeInfo)
{
if (typeInfo.Type != typeof(RoutingStateWorkaround))
{
return;
}

// This will be an issue if settings are serialized at any time other than
// application shutdown
typeInfo.OnSerializing = static obj =>
{
var navStack = ((RoutingState)obj).NavigationStack;
for (var i = navStack.Count - 1; i >= 0; --i)
{
if (navStack[i].GetType() == typeof(EditViewModel))
{
navStack.RemoveAt(i);
}
}
};
}

private static void UseDataContract(JsonTypeInfo typeInfo)
{
if (typeInfo.Type.GetCustomAttribute<DataContractAttribute>() is null)
{
return;
}

foreach (var propertyInfo in typeInfo.Properties)
{
if (propertyInfo.AttributeProvider is not ICustomAttributeProvider provider
|| !provider.GetCustomAttributes(true).Any(x => x is DataMemberAttribute))
{
propertyInfo.ShouldSerialize = static (_, _)
=> false;
}
}
}

private static void UseTypeNamesForViewModels(JsonTypeInfo typeInfo)
{
if (typeInfo.Type != typeof(IRoutableViewModel))
{
return;
}

typeInfo.PolymorphismOptions = new()
{
DerivedTypes =
{
new(typeof(SongViewModel), "vm_song"),
new(typeof(AddViewModel), "vm_add"),
},
};
}
}

public class RoutingStateWorkaround : RoutingState;
16 changes: 0 additions & 16 deletions src/SongProcessor.UI/NewtonsoftJsonSkipThis.cs

This file was deleted.

116 changes: 0 additions & 116 deletions src/SongProcessor.UI/NewtonsoftJsonSuspensionDriver.cs

This file was deleted.

13 changes: 6 additions & 7 deletions src/SongProcessor.UI/SongProcessor.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.5" />
<PackageReference Include="Avalonia" Version="11.2.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.5" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.5" />
<PackageReference Include="Avalonia.Themes.Simple" Version="11.1.5" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.3" />
<PackageReference Include="Avalonia.Themes.Simple" Version="11.2.3" />
<PackageReference Include="DynamicData" Version="9.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="ReactiveUI.Validation" Version="4.1.1" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/SongProcessor.UI/ViewModels/AddViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Collections.ObjectModel;
using System.Reactive;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace SongProcessor.UI.ViewModels;

Expand Down Expand Up @@ -107,6 +108,7 @@ public AddViewModel(
SelectDirectory = ReactiveCommand.CreateFromTask(SelectDirectoryAsync);
}

[JsonConstructor]
private AddViewModel() : this(
Locator.Current.GetService<IScreen>()!,
Locator.Current.GetService<ISongLoader>()!,
Expand Down
8 changes: 2 additions & 6 deletions src/SongProcessor.UI/ViewModels/EditViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Newtonsoft.Json;

using ReactiveUI;
using ReactiveUI;
using ReactiveUI.Validation.Abstractions;
using ReactiveUI.Validation.Contexts;
using ReactiveUI.Validation.Extensions;
Expand All @@ -14,8 +12,6 @@

namespace SongProcessor.UI.ViewModels;

// Never serialize this view/viewmodel since this data is related to folder structure
[JsonConverter(typeof(NewtonsoftJsonSkipThis))]
public sealed class EditViewModel : ReactiveObject, IRoutableViewModel, IValidatableViewModel
{
private readonly ObservableAnime _Anime;
Expand Down Expand Up @@ -118,7 +114,7 @@ public string Start
set => this.RaiseAndSetIfChanged(ref field, value);
}
public string UrlPathSegment => "/edit";
public ValidationContext ValidationContext { get; } = new();
public IValidationContext ValidationContext { get; } = new ValidationContext();
public int VideoTrack
{
get;
Expand Down
15 changes: 13 additions & 2 deletions src/SongProcessor.UI/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Avalonia.Input.Platform;

using DynamicData;

using ReactiveUI;

using SongProcessor.FFmpeg;
Expand All @@ -10,17 +12,25 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace SongProcessor.UI.ViewModels;

[DataContract]
public sealed class MainViewModel : ReactiveObject, IScreen
{
public RoutingState Router => RouterWorkaround;
[DataMember]
public RoutingState Router
public RoutingStateWorkaround RouterWorkaround
{
get;
set => this.RaiseAndSetIfChanged(ref field, value);
init
{
field.NavigationStack.Clear();
field.NavigationStack.AddRange(value.NavigationStack);
this.RaisePropertyChanged(nameof(RouterWorkaround));
this.RaisePropertyChanged(nameof(Router));
}
} = new();

#region Commands
Expand Down Expand Up @@ -57,6 +67,7 @@ public MainViewModel(
}, CanGoBack());
}

[JsonConstructor]
private MainViewModel() : this(
Locator.Current.GetService<ISongLoader>()!,
Locator.Current.GetService<ISongProcessor>()!,
Expand Down
2 changes: 2 additions & 0 deletions src/SongProcessor.UI/ViewModels/SongViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace SongProcessor.UI.ViewModels;

Expand Down Expand Up @@ -188,6 +189,7 @@ public SongViewModel(
CanNavigate = busy.CombineLatest(loaded, (x, y) => !(x || y));
}

[JsonConstructor]
private SongViewModel() : this(
Locator.Current.GetService<IScreen>()!,
Locator.Current.GetService<ISongLoader>()!,
Expand Down
1 change: 0 additions & 1 deletion tests/SongProcessor.Tests/SongProcessor.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit bcd323c

Please sign in to comment.