Add new service to handle profile migrations (#468)

* Add new service to handle profile migrations

* Handle various null checks

* Remove unecessary assignments

* Further works on this:

- Loads profiles as JObject's initally, so migration can take place on profiles that don't have proper compatability
- Adds prerequisite migrations, and sorts them after one another

* Throw exception if profile can't be deserialized after migration

* Cleanup & use profile version

* Further migrations work, support 3.10 & 3.11 profiles upgrading to 4.0

* Update parameter name
This commit is contained in:
Jesse
2025-07-11 14:11:02 +02:00
committed by GitHub
parent 12699d799f
commit 533a7356fd
11 changed files with 751 additions and 24 deletions
@@ -70,12 +70,6 @@ public class GameController(
return;
}
fullProfile.SptData ??= new Spt
{
//TODO: complete
Version = "Replace_me",
};
fullProfile.SptData.Migrations ??= new Dictionary<string, long>();
fullProfile.FriendProfileIds ??= [];
if (fullProfile.ProfileInfo?.IsWiped is not null && fullProfile.ProfileInfo.IsWiped.Value)
@@ -0,0 +1,41 @@
using System.Text.Json.Nodes;
using SPTarkov.Server.Core.Models.Eft.Profile;
namespace SPTarkov.Server.Core.Migration
{
public abstract class AbstractProfileMigration : IProfileMigration
{
public abstract string FromVersion { get; }
public abstract string ToVersion { get; }
public abstract string MigrationName { get; }
public abstract IEnumerable<Type> PrerequisiteMigrations { get; }
public abstract bool CanMigrate(
JsonObject profile,
IEnumerable<IProfileMigration> previouslyRanMigrations
);
public abstract JsonObject? Migrate(JsonObject profile);
public virtual bool PostMigrate(SptProfile profile)
{
return true;
}
protected SemanticVersioning.Version? GetProfileVersion(JsonObject profile)
{
var versionString = profile["spt"]?["version"]?.GetValue<string>();
if (versionString is null)
{
return null;
}
var versionNumber = versionString.Split(' ')[0];
return SemanticVersioning.Version.TryParse(versionNumber, out var version)
? version
: null;
}
}
}
@@ -0,0 +1,32 @@
using System.Text.Json.Nodes;
using SPTarkov.Server.Core.Models.Eft.Profile;
namespace SPTarkov.Server.Core.Migration
{
public interface IProfileMigration
{
/// <summary>
/// Allows for adding checks if the profile in question can migrate
/// </summary>
/// <param name="profile">The profile to check</param>
/// <returns>Returns true if the profile can migrate, returns false if not</returns>
public bool CanMigrate(
JsonObject profile,
IEnumerable<IProfileMigration> previouslyRanMigrations
);
/// <summary>
/// Migrate the profile, this should be used to handle and fix old data that has been removed from the <see cref="SptProfile"/> record
/// or a general incompatibility due to different typing
/// </summary>
/// <param name="profile">The profile to migrate</param>
/// <returns>Returns the migrated profile on success, or null if it failed</returns>
public JsonObject? Migrate(JsonObject profile);
/// <summary>
/// Handles post migration of the profile, this can be used to fill new types with (old) data gotten from <see cref="Migrate"/>
/// </summary>
/// <returns>Should return true if successful, should return false if not</returns>
public bool PostMigrate(SptProfile profile);
}
}
@@ -0,0 +1,60 @@
using System.Security.Cryptography;
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using Range = SemanticVersioning.Range;
namespace SPTarkov.Server.Core.Migration.Migrations
{
/// <summary>
/// In 0.16.1.3.35312 BSG changed this to from an int to a hex64 encoded value.
/// </summary>
[Injectable]
public class HideoutSeed : AbstractProfileMigration
{
public override string FromVersion
{
get { return "~3.10"; }
}
public override string ToVersion
{
get { return "3.11"; }
}
public override string MigrationName
{
get { return "HideoutSeed311-SPTSharp"; }
}
public override IEnumerable<Type> PrerequisiteMigrations
{
get { return []; }
}
public override bool CanMigrate(
JsonObject profile,
IEnumerable<IProfileMigration> previouslyRanMigrations
)
{
var profileVersion = GetProfileVersion(profile);
var fromRange = Range.Parse(FromVersion);
var versionMatches = fromRange.IsSatisfied(profileVersion);
var seedNode = profile["characters"]?["pmc"]?["Hideout"]?["Seed"];
var seedIsNumeric =
seedNode is JsonValue seedValue && seedValue.TryGetValue<long>(out _);
return versionMatches && seedIsNumeric;
}
public override JsonObject? Migrate(JsonObject profile)
{
profile["characters"]!["pmc"]!["Hideout"]!["Seed"] = Convert.ToHexStringLower(
RandomNumberGenerator.GetBytes(16)
);
return profile;
}
}
}
@@ -0,0 +1,85 @@
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Services;
using Range = SemanticVersioning.Range;
namespace SPTarkov.Server.Core.Migration.Migrations
{
/// <summary>
/// In 16.8.0.37972 BSG added customization for voices, technically this only affects BE profiles, but this should fix these.
/// </summary>
[Injectable]
public class TheVoices(DatabaseService databaseService) : AbstractProfileMigration
{
public override string FromVersion
{
get { return "~4.0"; }
}
public override string ToVersion
{
get { return "~4.0"; }
}
public override string MigrationName
{
get { return "TheVoices400"; }
}
public override IEnumerable<Type> PrerequisiteMigrations
{
// Requires ThreeTenToThreeEleven on legacy profiles, due to that changing customization for the first time
get { return [typeof(ThreeTenToThreeEleven)]; }
}
public override bool CanMigrate(
JsonObject profile,
IEnumerable<IProfileMigration> previouslyRanMigrations
)
{
bool voiceIsMissing = profile["characters"]?["pmc"]?["Customization"]?["Voice"] == null;
return voiceIsMissing;
}
public override JsonObject? Migrate(JsonObject profile)
{
HandlePmcVoice(profile);
HandleScavVoice(profile);
return profile;
}
private void HandlePmcVoice(JsonObject profileObject)
{
var pmcInfo = profileObject["characters"]!["pmc"]!["Info"] as JsonObject;
var oldVoice = pmcInfo["Voice"]?.ToString() ?? "";
pmcInfo.Remove("Voice");
var voiceMongoId = databaseService
.GetCustomization()
.FirstOrDefault(x => x.Value.Properties.Name == oldVoice)
.Key;
profileObject["characters"]!["pmc"]!["Customization"]!["Voice"] =
voiceMongoId.ToString();
}
private void HandleScavVoice(JsonObject profileObject)
{
var pmcInfo = profileObject["characters"]!["scav"]!["Info"] as JsonObject;
var oldVoice = pmcInfo["Voice"]?.ToString() ?? "";
pmcInfo.Remove("Voice");
var voiceMongoId = databaseService
.GetCustomization()
.FirstOrDefault(x => x.Value.Properties.Name == oldVoice)
.Key;
profileObject["characters"]!["scav"]!["Customization"]!["Voice"] =
voiceMongoId.ToString();
}
}
}
@@ -0,0 +1,82 @@
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Utils;
using Range = SemanticVersioning.Range;
namespace SPTarkov.Server.Core.Migration.Migrations
{
[Injectable]
public class ThreeElevenToFourZero(Watermark watermark) : AbstractProfileMigration
{
public override string FromVersion
{
get { return "~3.11"; }
}
public override string ToVersion
{
get { return "4.0"; }
}
public override string MigrationName
{
get { return "311x-SPTSharp"; }
}
public override IEnumerable<Type> PrerequisiteMigrations
{
get { return [typeof(ThreeTenToThreeEleven)]; }
}
public override bool CanMigrate(
JsonObject profile,
IEnumerable<IProfileMigration> previouslyRanMigrations
)
{
var profileVersion = GetProfileVersion(profile);
var fromRange = Range.Parse(FromVersion);
var versionMatches =
fromRange.IsSatisfied(profileVersion)
|| PrerequisiteMigrations.All(prereq =>
previouslyRanMigrations.Any(r => r.GetType() == prereq)
);
return versionMatches;
}
public override JsonObject? Migrate(JsonObject profile)
{
if (profile["characters"]!["pmc"]!["Hideout"]!["Production"] is JsonObject production)
{
foreach (var entry in production)
{
if (
entry.Value is JsonObject productionEntry
&& productionEntry["StartTimestamp"] is JsonValue startTimestampValue
&& startTimestampValue.TryGetValue<string>(out var startTimestampStr)
&& long.TryParse(startTimestampStr, out var startTimestampInt)
)
{
productionEntry["StartTimestamp"] = startTimestampInt;
}
}
}
//Todo: TaskConditionCounters CounterCrafting?
return profile;
}
public override bool PostMigrate(SptProfile profile)
{
profile.SptData.ExtraRepeatableQuests = [];
profile.SptData.Version = $"{watermark.GetVersionTag()} (Migrated from 3.11)";
return base.PostMigrate(profile);
}
}
}
@@ -0,0 +1,259 @@
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using Range = SemanticVersioning.Range;
namespace SPTarkov.Server.Core.Migration.Migrations
{
[Injectable]
public class ThreeTenToThreeEleven(
DatabaseService databaseService,
// Yes, referencing the helpers directly causes a circular dependency. Too bad!
IServiceProvider serviceProvider
) : AbstractProfileMigration
{
private List<string> _oldSuiteData = [];
public override string FromVersion
{
get { return "~3.10"; }
}
public override string ToVersion
{
get { return "3.11"; }
}
public override string MigrationName
{
get { return "310x-SPTSharp"; }
}
public override IEnumerable<Type> PrerequisiteMigrations
{
get { return [typeof(HideoutSeed)]; }
}
public override bool CanMigrate(
JsonObject profile,
IEnumerable<IProfileMigration> previouslyRanMigrations
)
{
var profileVersion = GetProfileVersion(profile);
var fromRange = Range.Parse(FromVersion);
var versionMatches = fromRange.IsSatisfied(profileVersion);
return versionMatches;
}
public override JsonObject? Migrate(JsonObject profile)
{
if (profile["suits"] is JsonArray suitsArray)
{
_oldSuiteData = suitsArray
.Select(node => node?.GetValue<string>())
.Where(suit => suit != null)
.ToList()!;
}
profile.Remove("suits");
return profile;
}
public override bool PostMigrate(SptProfile profile)
{
if (profile.CustomisationUnlocks is null)
{
profile.CustomisationUnlocks = [];
profile.AddCustomisationUnlocksToProfile();
}
if (profile.CharacterData.PmcData.Prestige is null)
{
profile.CharacterData.PmcData.Prestige = [];
}
if (profile.CharacterData.PmcData.Inventory.HideoutCustomizationStashId is null)
{
profile.CharacterData.PmcData.Inventory.HideoutCustomizationStashId = new(
"676db384777490e23c45b657"
);
//Directly injecting CreateProfileService causes a circular dependency which I can't be bothered to fix just for this
serviceProvider
.GetService<CreateProfileService>()!
.AddMissingInternalContainersToProfile(profile.CharacterData.PmcData);
}
if (profile.CharacterData.PmcData.Hideout.Customization is null)
{
profile.CharacterData.PmcData.Hideout.Customization = new Dictionary<
string,
Models.Common.MongoId
>
{
{ "Wall", new("675844bdf94a97cbbe096f1a") },
{ "Floor", new("6758443ff94a97cbbe096f18") },
{ "Light", new("675fe8abbc3deae49a0b947f") },
{ "Ceiling", new("673b3f977038192ee006aa09") },
{ "ShootingRangeMark", new("67585d416c72998cf60ed85a") },
};
}
if (profile.CharacterData.PmcData.Info.Side == "Bear")
{
ProcessBearProfile(profile);
}
if (profile.CharacterData.PmcData.Info.Side == "Usec")
{
ProcessUsecProfile(profile);
}
if (profile.CharacterData.PmcData.Achievements.Count > 0)
{
var achievementsDb = databaseService.GetTemplates().Achievements;
foreach (var achievementId in profile.CharacterData.PmcData.Achievements.Keys)
{
var achievementDb = achievementsDb.FirstOrDefault(a => a.Id == achievementId);
var rewards = achievementDb?.Rewards;
if (rewards == null)
{
continue;
}
// Only hand out the new hideout customization rewards.
var filteredRewards = rewards
.Where(r => r.Type == RewardType.CustomizationDirect)
.ToList();
//Directly injecting RewardHelper causes a circular dependency which I can't be bothered to fix just for this
serviceProvider
.GetService<RewardHelper>()!
.ApplyRewards(
filteredRewards,
CustomisationSource.ACHIEVEMENT,
profile,
profile.CharacterData.PmcData,
achievementId
);
}
}
return true;
}
private void ProcessBearProfile(SptProfile profile)
{
// Reset clothing customization back to default as customization changed in 3.11
profile.CharacterData.PmcData.Customization.Body = new("5cc0858d14c02e000c6bea66");
profile.CharacterData.PmcData.Customization.Feet = new("5cc085bb14c02e000e67a5c5");
profile.CharacterData.PmcData.Customization.Hands = new("5cc0876314c02e000c6bea6b");
profile.CharacterData.PmcData.Customization.DogTag = new("674731c8bafff850080488bb");
if (profile.CharacterData.PmcData.Info.GameVersion == "edge_of_darkness")
{
profile.CharacterData.PmcData.Customization.DogTag = new(
"6746fd09bafff85008048838"
);
}
if (profile.CharacterData.PmcData.Info.GameVersion == "unheard_edition")
{
profile.CharacterData.PmcData.Customization.DogTag = new(
"67471928d17d6431550563b5"
);
}
foreach (var oldSuite in _oldSuiteData)
{
// Default Bear clothing, dont need to add this
if (
oldSuite == "5cd946231388ce000d572fe3"
|| oldSuite == "5cd945d71388ce000a659dfb"
|| oldSuite == "666841a02537107dc508b704"
)
{
continue;
}
var trader = databaseService.GetTrader("5ac3b934156ae10c4430e83c");
var traderClothing = trader?.Suits?.FirstOrDefault(s => s.SuiteId == oldSuite);
if (traderClothing != null)
{
var clothingToAdd = new CustomisationStorage
{
Id = traderClothing.SuiteId,
Source = CustomisationSource.UNLOCKED_IN_GAME,
Type = CustomisationType.SUITE,
};
profile.CustomisationUnlocks.Add(clothingToAdd);
}
}
}
private void ProcessUsecProfile(SptProfile profile)
{
// Reset clothing customization back to default as customization changed in 3.11
profile.CharacterData.PmcData.Customization.Body = new("5cde95d97d6c8b647a3769b0"); //Usec default clothing
profile.CharacterData.PmcData.Customization.Feet = new("5cde95ef7d6c8b04713c4f2d");
profile.CharacterData.PmcData.Customization.Hands = new("5cde95fa7d6c8b04737c2d13");
profile.CharacterData.PmcData.Customization.DogTag = new("674731d1170146228c0d222a"); //Usec base dogtag
if (profile.CharacterData.PmcData.Info.GameVersion == "edge_of_darkness")
{
profile.CharacterData.PmcData.Customization.DogTag = new(
"67471938bafff850080488b7"
);
}
if (profile.CharacterData.PmcData.Info.GameVersion == "unheard_edition")
{
profile.CharacterData.PmcData.Customization.DogTag = new(
"6747193f170146228c0d2226"
);
}
foreach (var oldSuite in _oldSuiteData)
{
// Default Usec clothing, dont need to add this
if (
oldSuite == "5cde9ec17d6c8b04723cf479"
|| oldSuite == "5cde9e957d6c8b0474535da7"
|| oldSuite == "666841a02537107dc508b704"
)
{
continue;
}
var trader = databaseService.GetTrader("5ac3b934156ae10c4430e83c");
var traderClothing = trader?.Suits?.FirstOrDefault(s => s.SuiteId == oldSuite);
if (traderClothing != null)
{
var clothingToAdd = new CustomisationStorage
{
Id = traderClothing.SuiteId,
Source = CustomisationSource.UNLOCKED_IN_GAME,
Type = CustomisationType.SUITE,
};
profile.CustomisationUnlocks.Add(clothingToAdd);
}
}
}
}
}
@@ -1,5 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.DI;
using SPTarkov.Server.Core.Models.Common;
@@ -20,6 +22,7 @@ public class SaveServer(
JsonUtil jsonUtil,
HashUtil hashUtil,
ServerLocalisationService serverLocalisationService,
ProfileMigratorService profileMigratorService,
ISptLogger<SaveServer> logger,
ConfigServer configServer
)
@@ -220,7 +223,12 @@ public class SaveServer(
if (fileUtil.FileExists(filePath))
// File found, store in profiles[]
{
profiles[sessionID] = await jsonUtil.DeserializeFromFileAsync<SptProfile>(filePath);
var profile = await jsonUtil.DeserializeFromFileAsync<JsonObject>(filePath);
if (profile is not null)
{
profiles[sessionID] = profileMigratorService.HandlePendingMigrations(profile);
}
}
// Run callbacks
@@ -293,7 +293,7 @@ public class CreateProfileService(
/// DOES NOT check that stash exists
/// </summary>
/// <param name="pmcData"> Profile to check </param>
protected void AddMissingInternalContainersToProfile(PmcData pmcData)
public void AddMissingInternalContainersToProfile(PmcData pmcData)
{
if (
!pmcData.Inventory.Items.Any(item =>
@@ -0,0 +1,166 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Migration;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Services
{
[Injectable(InjectionType.Singleton)]
public class ProfileMigratorService(
IEnumerable<IProfileMigration> profileMigrations,
TimeUtil timeUtil,
ISptLogger<ProfileMigratorService> logger
)
{
private IEnumerable<AbstractProfileMigration> _sortedMigrations = [];
public SptProfile HandlePendingMigrations(JsonObject profile)
{
// On the initial run, begin sorting our migrations
// This will sort it so that any non prerequisite migrations go first
// And then all of the prerequisite ones.
if (!_sortedMigrations.Any())
{
_sortedMigrations = SortMigrations();
}
var profileId = profile["info"]?["id"]?.GetValue<string>();
// Profile is due for a wipe or a reset, do not continue here.
if (
profile["characters"]?["pmc"]?["Info"] == null
|| profile["characters"]?["scav"]?["Info"] == null
|| (profile["info"]?["wipe"]?.GetValue<bool>() == true)
)
{
return profile.Deserialize<SptProfile>(JsonUtil.JsonSerializerOptionsNoIndent)
?? throw new InvalidOperationException(
$"Could not deserialize the profile {profileId}"
);
;
}
var ranMigrations = new List<AbstractProfileMigration>();
foreach (var profileMigration in _sortedMigrations)
{
if (profileMigration.CanMigrate(profile, ranMigrations))
{
logger.Warning(
$"{profileId} has a pending profile migration: {profileMigration.MigrationName}"
);
var migratedProfile = profileMigration.Migrate(profile);
if (migratedProfile is not null)
{
profile = migratedProfile;
ranMigrations.Add(profileMigration);
}
}
}
var SptReadyProfile =
profile.Deserialize<SptProfile>(JsonUtil.JsonSerializerOptionsNoIndent)
?? throw new InvalidOperationException(
$"Could not deserialize the profile {profileId}"
);
foreach (var ranMigration in ranMigrations)
{
if (ranMigration.PostMigrate(SptReadyProfile))
{
logger.Success(
$"{profileId} successfully ran profile migration: {ranMigration.MigrationName}"
);
SptReadyProfile.SptData.Migrations.Add(
ranMigration.MigrationName,
timeUtil.GetTimeStamp()
);
}
}
return SptReadyProfile;
}
protected void SetCompletedMigration(JsonObject profile, string migrationName)
{
var profileMigrations = profile["spt"]["migrations"] as JsonObject;
profileMigrations[migrationName] = JsonValue.Create(timeUtil.GetTimeStamp());
}
protected IEnumerable<AbstractProfileMigration> SortMigrations()
{
var sortedMigrations = new List<AbstractProfileMigration>();
var visitedMigrations = new Dictionary<Type, bool>();
var migrationDict = profileMigrations
.Cast<AbstractProfileMigration>()
.ToDictionary(m => m.GetType());
foreach (var migration in profileMigrations.Cast<AbstractProfileMigration>())
{
VisitMigrationForSort(
migration,
migrationDict,
visitedMigrations,
sortedMigrations
);
}
return sortedMigrations;
}
protected void VisitMigrationForSort(
AbstractProfileMigration migration,
Dictionary<Type, AbstractProfileMigration> migrationTypeDictionary,
Dictionary<Type, bool> visitedTypeDictionary,
List<AbstractProfileMigration> sortedMigrations
)
{
var migrationType = migration.GetType();
if (visitedTypeDictionary.TryGetValue(migrationType, out var isVisited))
{
if (isVisited)
{
return;
}
// Big error, two migrations should never depend on one another
throw new InvalidOperationException(
$"Cycle detected in migration prerequisites involving: {migrationType.Name}"
);
}
// Mark the current migration type for visiting
visitedTypeDictionary[migrationType] = false;
foreach (var prerequisiteType in migration.PrerequisiteMigrations)
{
if (!migrationTypeDictionary.TryGetValue(prerequisiteType, out var prereqMigration))
{
continue;
}
// Visit the next prerequisite
VisitMigrationForSort(
prereqMigration,
migrationTypeDictionary,
visitedTypeDictionary,
sortedMigrations
);
}
// Done visiting, mark it as fully visited and add it to the sorted migrations
visitedTypeDictionary[migrationType] = true;
sortedMigrations.Add(migration);
}
}
}
@@ -9,12 +9,12 @@ namespace SPTarkov.Server.Core.Utils;
[Injectable(InjectionType.Singleton)]
public class JsonUtil
{
private static JsonSerializerOptions? _jsonSerializerOptionsIndented;
private static JsonSerializerOptions? _jsonSerializerOptionsNoIndent;
public static JsonSerializerOptions? JsonSerializerOptionsIndented { get; private set; }
public static JsonSerializerOptions? JsonSerializerOptionsNoIndent { get; private set; }
public JsonUtil(IEnumerable<IJsonConverterRegistrator> registrators)
{
_jsonSerializerOptionsNoIndent = new JsonSerializerOptions()
JsonSerializerOptionsNoIndent = new JsonSerializerOptions()
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
@@ -26,11 +26,11 @@ public class JsonUtil
{
foreach (var converter in registrator.GetJsonConverters())
{
_jsonSerializerOptionsNoIndent.Converters.Add(converter);
JsonSerializerOptionsNoIndent.Converters.Add(converter);
}
}
_jsonSerializerOptionsIndented = new JsonSerializerOptions(_jsonSerializerOptionsNoIndent)
JsonSerializerOptionsIndented = new JsonSerializerOptions(JsonSerializerOptionsNoIndent)
{
WriteIndented = true,
};
@@ -46,7 +46,7 @@ public class JsonUtil
{
return string.IsNullOrEmpty(json)
? default
: JsonSerializer.Deserialize<T>(json, _jsonSerializerOptionsNoIndent);
: JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -59,7 +59,7 @@ public class JsonUtil
{
return string.IsNullOrEmpty(json)
? null
: JsonSerializer.Deserialize(json, type, _jsonSerializerOptionsNoIndent);
: JsonSerializer.Deserialize(json, type, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -76,7 +76,7 @@ public class JsonUtil
using (FileStream fs = new(file, FileMode.Open, FileAccess.Read))
{
return JsonSerializer.Deserialize<T>(fs, _jsonSerializerOptionsNoIndent);
return JsonSerializer.Deserialize<T>(fs, JsonSerializerOptionsNoIndent);
}
}
@@ -101,7 +101,7 @@ public class JsonUtil
useAsync: true
);
return await JsonSerializer.DeserializeAsync<T>(fs, _jsonSerializerOptionsNoIndent);
return await JsonSerializer.DeserializeAsync<T>(fs, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -119,7 +119,7 @@ public class JsonUtil
using (FileStream fs = new(file, FileMode.Open, FileAccess.Read))
{
return JsonSerializer.Deserialize(fs, type, _jsonSerializerOptionsNoIndent);
return JsonSerializer.Deserialize(fs, type, JsonSerializerOptionsNoIndent);
}
}
@@ -145,7 +145,7 @@ public class JsonUtil
useAsync: true
);
return await JsonSerializer.DeserializeAsync(fs, type, _jsonSerializerOptionsNoIndent);
return await JsonSerializer.DeserializeAsync(fs, type, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -156,7 +156,7 @@ public class JsonUtil
/// <returns></returns>
public object? DeserializeFromFileStream(FileStream fs, Type type)
{
return JsonSerializer.Deserialize(fs, type, _jsonSerializerOptionsNoIndent);
return JsonSerializer.Deserialize(fs, type, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -167,7 +167,7 @@ public class JsonUtil
/// <returns></returns>
public async Task<object?> DeserializeFromFileStreamAsync(FileStream fs, Type type)
{
return await JsonSerializer.DeserializeAsync(fs, type, _jsonSerializerOptionsNoIndent);
return await JsonSerializer.DeserializeAsync(fs, type, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -177,7 +177,7 @@ public class JsonUtil
/// <returns>T</returns>
public async Task<T?> DeserializeFromMemoryStreamAsync<T>(MemoryStream ms)
{
return await JsonSerializer.DeserializeAsync<T>(ms, _jsonSerializerOptionsNoIndent);
return await JsonSerializer.DeserializeAsync<T>(ms, JsonSerializerOptionsNoIndent);
}
/// <summary>
@@ -193,7 +193,7 @@ public class JsonUtil
? null
: JsonSerializer.Serialize(
obj,
indented ? _jsonSerializerOptionsIndented : _jsonSerializerOptionsNoIndent
indented ? JsonSerializerOptionsIndented : JsonSerializerOptionsNoIndent
);
}
@@ -211,7 +211,7 @@ public class JsonUtil
: JsonSerializer.Serialize(
obj,
type,
indented ? _jsonSerializerOptionsIndented : _jsonSerializerOptionsNoIndent
indented ? JsonSerializerOptionsIndented : JsonSerializerOptionsNoIndent
);
}
}