From a37eb16b1fb4d776690f1a79393bc1b9f9234be4 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 18 Jan 2023 13:20:37 +1100 Subject: [PATCH 1/3] add support for DriveInfo, FileInfo, and DirectoryInfo --- .../Converters/DirectoryInfoConverter.cs | 21 ++++++ src/Argon/Converters/DriveInfoConverter.cs | 21 ++++++ src/Argon/Converters/FileInfoConverter.cs | 21 ++++++ .../Serialization/DefaultContractResolver.cs | 22 +----- src/Tests/Converters/IoInfos.cs | 71 +++++++++++++++++++ src/Tests/Issues/Issue1541.cs | 39 ---------- 6 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 src/Argon/Converters/DirectoryInfoConverter.cs create mode 100644 src/Argon/Converters/DriveInfoConverter.cs create mode 100644 src/Argon/Converters/FileInfoConverter.cs create mode 100644 src/Tests/Converters/IoInfos.cs delete mode 100644 src/Tests/Issues/Issue1541.cs diff --git a/src/Argon/Converters/DirectoryInfoConverter.cs b/src/Argon/Converters/DirectoryInfoConverter.cs new file mode 100644 index 000000000..9ddc4a375 --- /dev/null +++ b/src/Argon/Converters/DirectoryInfoConverter.cs @@ -0,0 +1,21 @@ +class DirectoryInfoConverter : + JsonConverter +{ + public override void WriteJson(JsonWriter writer, DirectoryInfo? value, JsonSerializer serializer) + { + if (value != null) + { + writer.WriteValue(value.FullName.Replace('\\', '/')); + } + } + + public override DirectoryInfo? ReadJson(JsonReader reader, Type type, DirectoryInfo? existingValue, bool hasExisting, JsonSerializer serializer) + { + if (reader.Value is string value) + { + return new(value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Argon/Converters/DriveInfoConverter.cs b/src/Argon/Converters/DriveInfoConverter.cs new file mode 100644 index 000000000..d39cf32cd --- /dev/null +++ b/src/Argon/Converters/DriveInfoConverter.cs @@ -0,0 +1,21 @@ +class DriveInfoConverter : + JsonConverter +{ + public override void WriteJson(JsonWriter writer, DriveInfo? value, JsonSerializer serializer) + { + if (value != null) + { + writer.WriteValue(value.Name.Replace('\\', '/')); + } + } + + public override DriveInfo? ReadJson(JsonReader reader, Type type, DriveInfo? existingValue, bool hasExisting, JsonSerializer serializer) + { + if (reader.Value is string value) + { + return new(value); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Argon/Converters/FileInfoConverter.cs b/src/Argon/Converters/FileInfoConverter.cs new file mode 100644 index 000000000..8822bc38b --- /dev/null +++ b/src/Argon/Converters/FileInfoConverter.cs @@ -0,0 +1,21 @@ +class FileInfoConverter : + JsonConverter +{ + public override void WriteJson(JsonWriter writer, FileInfo? value, JsonSerializer serializer) + { + if (value != null) + { + writer.WriteValue(value.FullName.Replace('\\', '/')); + } + } + + public override FileInfo? ReadJson(JsonReader reader, Type type, FileInfo? existingValue, bool hasExisting, JsonSerializer serializer) + { + if (reader.Value is string value) + { + return new(value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Argon/Serialization/DefaultContractResolver.cs b/src/Argon/Serialization/DefaultContractResolver.cs index 9deabffb4..d6099e7d1 100644 --- a/src/Argon/Serialization/DefaultContractResolver.cs +++ b/src/Argon/Serialization/DefaultContractResolver.cs @@ -12,19 +12,14 @@ public class DefaultContractResolver : IContractResolver // Json.NET Schema requires a property internal static IContractResolver Instance { get; } = new DefaultContractResolver(); - - static readonly string[] blacklistedTypeNames = - { - "System.IO.DriveInfo", - "System.IO.FileInfo", - "System.IO.DirectoryInfo" - }; - static readonly JsonConverter[] builtInConverters = { new ExpandoObjectConverter(), new DiscriminatedUnionConverter(), new KeyValuePairConverter(), + new DriveInfoConverter(), + new DirectoryInfoConverter(), + new FileInfoConverter(), new RegexConverter() }; @@ -237,20 +232,9 @@ protected virtual JsonObjectContract CreateObjectContract(Type type) SetExtensionDataDelegates(contract, extensionDataMember); } - // serializing DirectoryInfo without ISerializable will stackoverflow - // https://github.com/JamesNK/Newtonsoft.Json/issues/1541 - if (Array.IndexOf(blacklistedTypeNames, type.FullName) != -1) - { - contract.OnSerializingCallbacks.Add(ThrowUnableToSerializeError); - } - return contract; } - [DoesNotReturn] - static void ThrowUnableToSerializeError(object o, StreamingContext context) => - throw new JsonSerializationException($"Unable to serialize instance of '{o.GetType()}'."); - static MemberInfo? GetExtensionDataMemberForType(Type type) { var members = GetClassHierarchyForType(type).SelectMany(baseType => diff --git a/src/Tests/Converters/IoInfos.cs b/src/Tests/Converters/IoInfos.cs new file mode 100644 index 000000000..7a05fb559 --- /dev/null +++ b/src/Tests/Converters/IoInfos.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2007 James Newton-King. All rights reserved. +// Use of this source code is governed by The MIT License, +// as found in the license.md file. + +#nullable enable +public class IoInfos : TestFixtureBase +{ + [Fact] + public void Test_DirectoryInfo() + { + var info = new DirectoryInfo(@"c:\dir\one"); + var serialized = JsonConvert.SerializeObject(info); + Assert.Equal($"\"{info.FullName.Replace('\\', '/')}\"", serialized); + var result = JsonConvert.DeserializeObject(serialized); + Assert.Equal(info.FullName, result.FullName); + } + + [Fact] + public void Test_FileInfo() + { + var info = new FileInfo(@"d:\large.json"); + var serialized = JsonConvert.SerializeObject(info); + Assert.Equal($"\"{info.FullName.Replace('\\', '/')}\"", serialized); + var result = JsonConvert.DeserializeObject(serialized); + Assert.Equal(info.FullName, result.FullName); + } + + [Fact] + public void Test_DriveInfo() + { + var info = new DriveInfo(@"D:\"); + + var serialized = JsonConvert.SerializeObject(info); + Assert.Equal($"\"{info.Name.Replace('\\', '/')}\"", serialized); + var result = JsonConvert.DeserializeObject(serialized); + Assert.Equal(info.Name, result.Name); + } + + [Fact] + public void Nested() + { + var target = new Target + { + DirectoryInfo = new(@"c:\dir\one"), + DriveInfo = new(@"D:\"), + FileInfo = new(@"d:\large.json") + }; + var result = JsonConvert.SerializeObject(target); + Assert.Equal("""{"DirectoryInfo":"c:/dir/one","FileInfo":"d:/large.json","DriveInfo":"D:/"}""", result); + var deserialize = JsonConvert.DeserializeObject(result); + + Assert.Equal(target.DirectoryInfo.FullName, deserialize.DirectoryInfo!.FullName); + Assert.Equal(target.DriveInfo.Name, deserialize.DriveInfo!.Name); + Assert.Equal(target.FileInfo.FullName, deserialize.FileInfo!.FullName); + } + + [Fact] + public void NestedNull() + { + var result = JsonConvert.SerializeObject(new Target()); + Assert.Equal("""{"DirectoryInfo":null,"FileInfo":null,"DriveInfo":null}""", result); + JsonConvert.DeserializeObject(result); + } + + public class Target + { + public DirectoryInfo? DirectoryInfo { get; set; } + public FileInfo? FileInfo { get; set; } + public DriveInfo? DriveInfo { get; set; } + } +} diff --git a/src/Tests/Issues/Issue1541.cs b/src/Tests/Issues/Issue1541.cs deleted file mode 100644 index a6c3da0a8..000000000 --- a/src/Tests/Issues/Issue1541.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2007 James Newton-King. All rights reserved. -// Use of this source code is governed by The MIT License, -// as found in the license.md file. - -#if NET5_0_OR_GREATER - -public class Issue1541 : TestFixtureBase -{ - [Fact] - public void Test_DirectoryInfo() - { - var fileInfo = new FileInfo("large.json"); - - XUnitAssert.Throws( - () => JsonConvert.SerializeObject(fileInfo.Directory), - "Unable to serialize instance of 'System.IO.DirectoryInfo'."); - } - - [Fact] - public void Test_FileInfo() - { - var fileInfo = new FileInfo("large.json"); - - XUnitAssert.Throws( - () => JsonConvert.SerializeObject(fileInfo), - "Unable to serialize instance of 'System.IO.FileInfo'."); - } - - [Fact] - public void Test_DriveInfo() - { - var drive = DriveInfo.GetDrives()[0]; - - XUnitAssert.Throws( - () => JsonConvert.SerializeObject(drive), - "Unable to serialize instance of 'System.IO.DriveInfo'."); - } -} -#endif \ No newline at end of file From 2966f5c2a6bfb3ac601602e01e8d0c21af17b0f5 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 18 Jan 2023 13:47:52 +1100 Subject: [PATCH 2/3] . --- .../Converters/DirectoryInfoConverter.cs | 2 +- src/Argon/Converters/FileInfoConverter.cs | 2 +- src/Tests/Converters/IoInfos.cs | 38 ++++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/Argon/Converters/DirectoryInfoConverter.cs b/src/Argon/Converters/DirectoryInfoConverter.cs index 9ddc4a375..132b03fd8 100644 --- a/src/Argon/Converters/DirectoryInfoConverter.cs +++ b/src/Argon/Converters/DirectoryInfoConverter.cs @@ -5,7 +5,7 @@ public override void WriteJson(JsonWriter writer, DirectoryInfo? value, JsonSeri { if (value != null) { - writer.WriteValue(value.FullName.Replace('\\', '/')); + writer.WriteValue(value.ToString().Replace('\\', '/')); } } diff --git a/src/Argon/Converters/FileInfoConverter.cs b/src/Argon/Converters/FileInfoConverter.cs index 8822bc38b..03ec46e4d 100644 --- a/src/Argon/Converters/FileInfoConverter.cs +++ b/src/Argon/Converters/FileInfoConverter.cs @@ -5,7 +5,7 @@ public override void WriteJson(JsonWriter writer, FileInfo? value, JsonSerialize { if (value != null) { - writer.WriteValue(value.FullName.Replace('\\', '/')); + writer.WriteValue(value.ToString().Replace('\\', '/')); } } diff --git a/src/Tests/Converters/IoInfos.cs b/src/Tests/Converters/IoInfos.cs index 7a05fb559..98ee1c508 100644 --- a/src/Tests/Converters/IoInfos.cs +++ b/src/Tests/Converters/IoInfos.cs @@ -5,35 +5,35 @@ #nullable enable public class IoInfos : TestFixtureBase { + + static DriveInfo driveInfo = DriveInfo.GetDrives()[0]; + static FileInfo fileInfo = new($"one{Path.DirectorySeparatorChar}two.txt"); + static DirectoryInfo directoryInfo = new(Path.Combine($"one{Path.DirectorySeparatorChar}two")); + [Fact] public void Test_DirectoryInfo() { - var info = new DirectoryInfo(@"c:\dir\one"); - var serialized = JsonConvert.SerializeObject(info); - Assert.Equal($"\"{info.FullName.Replace('\\', '/')}\"", serialized); + var serialized = JsonConvert.SerializeObject(directoryInfo); + Assert.Equal("\"one/two\"", serialized); var result = JsonConvert.DeserializeObject(serialized); - Assert.Equal(info.FullName, result.FullName); + Assert.Equal(directoryInfo.FullName, result.FullName); } [Fact] public void Test_FileInfo() { - var info = new FileInfo(@"d:\large.json"); - var serialized = JsonConvert.SerializeObject(info); - Assert.Equal($"\"{info.FullName.Replace('\\', '/')}\"", serialized); + var serialized = JsonConvert.SerializeObject(fileInfo); + Assert.Equal("\"one/two.txt\"", serialized); var result = JsonConvert.DeserializeObject(serialized); - Assert.Equal(info.FullName, result.FullName); + Assert.Equal(fileInfo.FullName, result.FullName); } - [Fact] public void Test_DriveInfo() { - var info = new DriveInfo(@"D:\"); - - var serialized = JsonConvert.SerializeObject(info); - Assert.Equal($"\"{info.Name.Replace('\\', '/')}\"", serialized); + var serialized = JsonConvert.SerializeObject(driveInfo); + Assert.Equal($"\"{driveInfo.Name.Replace('\\', '/')}\"", serialized); var result = JsonConvert.DeserializeObject(serialized); - Assert.Equal(info.Name, result.Name); + Assert.Equal(driveInfo.Name, result.Name); } [Fact] @@ -41,17 +41,19 @@ public void Nested() { var target = new Target { - DirectoryInfo = new(@"c:\dir\one"), - DriveInfo = new(@"D:\"), - FileInfo = new(@"d:\large.json") + DirectoryInfo = directoryInfo, + DriveInfo = driveInfo, + FileInfo = fileInfo }; var result = JsonConvert.SerializeObject(target); - Assert.Equal("""{"DirectoryInfo":"c:/dir/one","FileInfo":"d:/large.json","DriveInfo":"D:/"}""", result); + Assert.Equal($$"""{"DirectoryInfo":"one/two","FileInfo":"one/two.txt","DriveInfo":"{{driveInfo.Name.Replace('\\', '/')}}"}""", result); var deserialize = JsonConvert.DeserializeObject(result); Assert.Equal(target.DirectoryInfo.FullName, deserialize.DirectoryInfo!.FullName); + Assert.Equal(target.DirectoryInfo.ToString(), deserialize.DirectoryInfo!.ToString()); Assert.Equal(target.DriveInfo.Name, deserialize.DriveInfo!.Name); Assert.Equal(target.FileInfo.FullName, deserialize.FileInfo!.FullName); + Assert.Equal(target.FileInfo.ToString(), deserialize.FileInfo!.ToString()); } [Fact] From 6ae48d6ef7aa80050cb7adf463e89424ad59f99f Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 18 Jan 2023 13:55:32 +1100 Subject: [PATCH 3/3] Update IoInfos.cs --- src/Tests/Converters/IoInfos.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tests/Converters/IoInfos.cs b/src/Tests/Converters/IoInfos.cs index 98ee1c508..c389fec24 100644 --- a/src/Tests/Converters/IoInfos.cs +++ b/src/Tests/Converters/IoInfos.cs @@ -27,6 +27,7 @@ public void Test_FileInfo() var result = JsonConvert.DeserializeObject(serialized); Assert.Equal(fileInfo.FullName, result.FullName); } + [Fact] public void Test_DriveInfo() { @@ -70,4 +71,4 @@ public class Target public FileInfo? FileInfo { get; set; } public DriveInfo? DriveInfo { get; set; } } -} +} \ No newline at end of file