Skip to content

Commit 11f5f90

Browse files
Switch to System.Text.Json (#89)
Co-authored-by: Dan Rigby <[email protected]>
1 parent 0aef377 commit 11f5f90

File tree

8 files changed

+108
-76
lines changed

8 files changed

+108
-76
lines changed

JsonFeedNet.Tests/JsonFeedTests.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ public void RoundtripSimple()
1111
string inputJsonFeed = TestExtensions.GetResourceAsString("Simple.json").NormalizeEndings();
1212
JsonFeed jsonFeed = JsonFeed.Parse(inputJsonFeed);
1313
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
14+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
1415

15-
Assert.Equal(inputJsonFeed, outputJsonFeed);
16+
Assert.Equivalent(jsonFeed, jsonFeed2);
1617
}
1718

1819
[Fact]
@@ -21,8 +22,9 @@ public void RoundtripDaringFireballBlog()
2122
string inputJsonFeed = TestExtensions.GetResourceAsString("DaringFireballBlog.json").NormalizeEndings();
2223
JsonFeed jsonFeed = JsonFeed.Parse(inputJsonFeed);
2324
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
24-
25-
Assert.Equal(inputJsonFeed.Length, outputJsonFeed.Length);
25+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
26+
27+
Assert.Equivalent(jsonFeed, jsonFeed2);
2628
}
2729

2830
[Fact]
@@ -31,8 +33,9 @@ public void RoundtripHyperCriticalBlog()
3133
string inputJsonFeed = TestExtensions.GetResourceAsString("HyperCriticalBlog.json").NormalizeEndings();
3234
JsonFeed jsonFeed = JsonFeed.Parse(inputJsonFeed);
3335
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
34-
35-
Assert.Equal(inputJsonFeed.Length, outputJsonFeed.Length);
36+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
37+
38+
Assert.Equivalent(jsonFeed, jsonFeed2);
3639
}
3740

3841
[Fact]
@@ -41,8 +44,9 @@ public void RoundtripMaybePizzaBlog()
4144
string inputJsonFeed = TestExtensions.GetResourceAsString("MaybePizzaBlog.json").NormalizeEndings();
4245
JsonFeed jsonFeed = JsonFeed.Parse(inputJsonFeed);
4346
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
44-
45-
Assert.Equal(inputJsonFeed.Length, outputJsonFeed.Length);
47+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
48+
49+
Assert.Equivalent(jsonFeed, jsonFeed2);
4650
}
4751

4852
[Fact]
@@ -51,8 +55,9 @@ public void RoundtripTheRecordPodcast()
5155
string inputJsonFeed = TestExtensions.GetResourceAsString("TheRecordPodcast.json").NormalizeEndings();
5256
JsonFeed jsonFeed = JsonFeed.Parse(inputJsonFeed);
5357
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
54-
55-
Assert.Equal(inputJsonFeed.Length, outputJsonFeed.Length);
58+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
59+
60+
Assert.Equivalent(jsonFeed, jsonFeed2);
5661
}
5762

5863
[Fact]
@@ -61,8 +66,9 @@ public void RoundtripTimeTablePodcast()
6166
string inputJsonFeed = TestExtensions.GetResourceAsString("TimeTablePodcast.json").NormalizeEndings();
6267
JsonFeed jsonFeed = JsonFeed.Parse(inputJsonFeed);
6368
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
64-
65-
Assert.Equal(inputJsonFeed.Length, outputJsonFeed.Length);
69+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
70+
71+
Assert.Equivalent(jsonFeed, jsonFeed2);
6672
}
6773

6874
[Fact]
@@ -79,8 +85,9 @@ public void VersionOneDotOne()
7985
Assert.Single(jsonFeed.Items[0].Authors);
8086
Assert.Equal("John Gruber", jsonFeed.Items[0].Authors[0].Name);
8187

82-
Assert.Equal(inputJsonFeed, outputJsonFeed);
83-
Assert.Equal(inputJsonFeed.Length, outputJsonFeed.Length);
88+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
89+
90+
Assert.Equivalent(jsonFeed, jsonFeed2);
8491
}
8592

8693
[Fact]
@@ -107,8 +114,11 @@ public async Task ParseFromUriAsyncMakesNetworkRequestAndDeserializesOutput()
107114
//When
108115
JsonFeed? jsonFeed = await JsonFeed.ParseFromUriAsync(contentUri, handler);
109116

117+
string outputJsonFeed = jsonFeed.Write().NormalizeEndings();
118+
JsonFeed jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
119+
110120
//Then
111-
Assert.Equal(inputJsonFeed.NormalizeEndings(), jsonFeed.Write().NormalizeEndings());
121+
Assert.Equivalent(jsonFeed, jsonFeed2);
112122
}
113123

114124
[Fact]
@@ -161,6 +171,7 @@ public void WriteFeedToStream()
161171
using StreamReader reader = new(memoryStream);
162172
string outputJsonFeed = reader.ReadToEnd().NormalizeEndings();
163173

164-
Assert.Equal(inputJsonFeed, outputJsonFeed);
174+
var jsonFeed2 = JsonFeed.Parse(outputJsonFeed);
175+
Assert.Equivalent(jsonFeed, jsonFeed2);
165176
}
166177
}

JsonFeedNet/JsonFeed.cs

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,53 @@
1-
namespace JsonFeedNet;
1+
using System.Text.Json;
2+
3+
namespace JsonFeedNet;
24

35
using System.Text;
4-
using Newtonsoft.Json;
6+
using System.Text.Json.Serialization;
57

68
// ReSharper disable MemberCanBePrivate.Global
79
// ReSharper disable UnusedMember.Global
810
public class JsonFeed
911
{
10-
private static readonly JsonSerializerSettings s_serializerSettings = new()
11-
{
12-
Formatting = Formatting.Indented,
13-
NullValueHandling = NullValueHandling.Ignore,
14-
DateFormatHandling = DateFormatHandling.IsoDateFormat
15-
};
16-
1712
/// <summary>
1813
/// The URL of the version of the format the feed uses.
1914
/// </summary>
20-
[JsonProperty("version")]
15+
[JsonPropertyName("version")]
2116
public string Version { get; set; } = "https://jsonfeed.org/version/1.1"; //required
2217

2318
/// <summary>
2419
/// The name of the feed.
2520
/// This will often correspond to the name of the website(blog, for instance).
2621
/// </summary>
27-
[JsonProperty("title")]
22+
[JsonPropertyName("title")]
2823
public string Title { get; set; } //required
2924

3025
/// <summary>
3126
/// The URL of the resource that the feed describes.
3227
/// This resource may or may not actually be a “home” page, but it should be an HTML page.
3328
/// </summary>
34-
[JsonProperty("home_page_url")]
29+
[JsonPropertyName("home_page_url")]
3530
public string HomePageUrl { get; set; } //optional
3631

3732
/// <summary>
3833
/// The URL of the feed.
3934
/// Serves as the unique identifier for the feed.
4035
/// </summary>
41-
[JsonProperty("feed_url")]
36+
[JsonPropertyName("feed_url")]
4237
public string FeedUrl { get; set; } //optional
4338

4439
/// <summary>
4540
/// More detail, beyond the title, on what the feed is about.
4641
/// A feed reader may display this text.
4742
/// </summary>
48-
[JsonProperty("description")]
43+
[JsonPropertyName("description")]
4944
public string Description { get; set; } //optional
5045

5146
/// <summary>
5247
/// Description of the purpose of the feed.
5348
/// This is for the use of people looking at the raw JSON, and should be ignored by feed readers.
5449
/// </summary>
55-
[JsonProperty("user_comment")]
50+
[JsonPropertyName("user_comment")]
5651
public string UserComment { get; set; } //optional
5752

5853
/// <summary>
@@ -61,64 +56,64 @@ public class JsonFeed
6156
/// won’t use it very often.
6257
/// Must not be the same as FeedUrl, and it must not be the same as a previous NextUrl (to avoid infinite loops).
6358
/// </summary>
64-
[JsonProperty("next_url")]
59+
[JsonPropertyName("next_url")]
6560
public string NextUrl { get; set; } //optional
6661

6762
/// <summary>
6863
/// The URL of an image for the feed suitable to be used in a timeline, much the way an avatar might be used.
6964
/// It should be square and relatively large — such as 512 x 512 — so that it can be scaled-down.
7065
/// It should use transparency where appropriate, since it may be rendered on a non-white background.
7166
/// </summary>
72-
[JsonProperty("icon")]
67+
[JsonPropertyName("icon")]
7368
public string Icon { get; set; } //optional
7469

7570
/// <summary>
7671
/// The URL of an image for the feed suitable to be used in a source list.
7772
/// It should be square and relatively small, but not smaller than 64 x 64.
7873
/// It should use transparency where appropriate, since it may be rendered on a non-white background.
7974
/// </summary>
80-
[JsonProperty("favicon")]
75+
[JsonPropertyName("favicon")]
8176
public string FavIcon { get; set; } //optional
8277

8378
/// <summary>
8479
/// The feed author.
8580
/// </summary>
86-
[JsonProperty("author")]
81+
[JsonPropertyName("author")]
8782
[Obsolete("obsolete by specification version 1.1. Use `Authors`")]
8883
public JsonFeedAuthor Author { get; set; } //optional
8984

9085
/// <summary>
9186
/// Specifies one or more feed authors.
9287
/// </summary>
93-
[JsonProperty("authors")]
88+
[JsonPropertyName("authors")]
9489
public List<JsonFeedAuthor> Authors { get; set; } //optional
9590

9691
/// <summary>
9792
/// Primary language for the feed in the format specified in RFC 5646.
9893
/// The value is usually a 2-letter language tag from ISO 639-1, optionally followed by a region tag.
9994
/// (Examples: en or en-US.)
10095
/// </summary>
101-
[JsonProperty("language")]
96+
[JsonPropertyName("language")]
10297
public string Language { get; set; } //optional
10398

10499
/// <summary>
105100
/// Indicates whether or not the feed is finished — that is, whether or not it will ever update again.
106101
/// If the value is true, then it’s expired. Any other value, or the absence of expired, means the feed may continue to
107102
/// update.
108103
/// </summary>
109-
[JsonProperty("expired")]
104+
[JsonPropertyName("expired")]
110105
public bool? Expired { get; set; } //optional
111106

112107
/// <summary>
113108
/// Endpoints that can be used to subscribe to real-time notifications of changes to this feed.
114109
/// </summary>
115-
[JsonProperty("hubs")]
110+
[JsonPropertyName("hubs")]
116111
public List<JsonFeedHub> Hubs { get; set; } //optional
117112

118113
/// <summary>
119114
/// The individual items in the feed.
120115
/// </summary>
121-
[JsonProperty("items")]
116+
[JsonPropertyName("items")]
122117
public List<JsonFeedItem> Items { get; set; } //required
123118

124119
/// <summary>
@@ -128,7 +123,7 @@ public class JsonFeed
128123
/// <returns>A JsonFeed object representing the parsed feed.</returns>
129124
public static JsonFeed Parse(string jsonFeedString)
130125
{
131-
return JsonConvert.DeserializeObject<JsonFeed>(jsonFeedString);
126+
return JsonSerializer.Deserialize<JsonFeed>(jsonFeedString, SourceGenerationContext.Default.JsonFeed);
132127
}
133128

134129
/// <summary>
@@ -142,7 +137,14 @@ public static async Task<JsonFeed> ParseFromUriAsync(Uri jsonFeedUri, HttpMessag
142137
HttpClient client = new(httpMessageHandler ?? new HttpClientHandler());
143138
string jsonDocument = await client.GetStringAsync(jsonFeedUri);
144139

145-
return Parse(jsonDocument);
140+
try
141+
{
142+
return Parse(jsonDocument);
143+
}
144+
finally
145+
{
146+
client.Dispose();
147+
}
146148
}
147149

148150
/// <summary>
@@ -172,6 +174,6 @@ public void Write(Stream stream)
172174
/// <returns>A string containing the generated feed JSON.</returns>
173175
public override string ToString()
174176
{
175-
return JsonConvert.SerializeObject(this, s_serializerSettings);
177+
return JsonSerializer.Serialize(this, SourceGenerationContext.Default.JsonFeed);
176178
}
177179
}

JsonFeedNet/JsonFeedAttachment.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace JsonFeedNet;
22

3-
using Newtonsoft.Json;
3+
using System.Text.Json.Serialization;
44

55
/// <summary>
66
/// A related resource to a feed.
@@ -10,13 +10,13 @@ public class JsonFeedAttachment
1010
/// <summary>
1111
/// The location of the attachment.
1212
/// </summary>
13-
[JsonProperty("url")]
13+
[JsonPropertyName("url")]
1414
public string Url { get; set; } //required
1515

1616
/// <summary>
1717
/// The mime type of the attachment, such as “audio/mpeg.”
1818
/// </summary>
19-
[JsonProperty("mime_type")]
19+
[JsonPropertyName("mime_type")]
2020
public string MimeType { get; set; } //required
2121

2222
/// <summary>
@@ -25,18 +25,18 @@ public class JsonFeedAttachment
2525
/// of the same thing.
2626
/// In this way a podcaster, for instance, might provide an audio recording in different formats.
2727
/// </summary>
28-
[JsonProperty("title")]
28+
[JsonPropertyName("title")]
2929
public string Title { get; set; } //optional
3030

3131
/// <summary>
3232
/// How large the file is.
3333
/// </summary>
34-
[JsonProperty("size_in_bytes")]
34+
[JsonPropertyName("size_in_bytes")]
3535
public long? SizeInBytes { get; set; } //optional
3636

3737
/// <summary>
3838
/// How long the attachment takes to listen to or watch.
3939
/// </summary>
40-
[JsonProperty("duration_in_seconds")]
40+
[JsonPropertyName("duration_in_seconds")]
4141
public long? DurationInSeconds { get; set; } //optional
4242
}

JsonFeedNet/JsonFeedAuthor.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace JsonFeedNet;
22

3-
using Newtonsoft.Json;
3+
using System.Text.Json.Serialization;
44

55
/// <summary>
66
/// A feed author.
@@ -10,21 +10,21 @@ public class JsonFeedAuthor
1010
/// <summary>
1111
/// The author's name.
1212
/// </summary>
13-
[JsonProperty("name")]
13+
[JsonPropertyName("name")]
1414
public string Name { get; set; } //optional
1515

1616
/// <summary>
1717
/// The URL of a site owned by the author.
1818
/// It could be a blog, micro-blog, Twitter account, and so on.
1919
/// </summary>
20-
[JsonProperty("url")]
20+
[JsonPropertyName("url")]
2121
public string Url { get; set; } //optional
2222

2323
/// <summary>
2424
/// The URL for an image for the author.
2525
/// It should be square and relatively large — such as 512 x 512.
2626
/// It should use transparency where appropriate, since it may be rendered on a non-white background.
2727
/// </summary>
28-
[JsonProperty("avatar")]
28+
[JsonPropertyName("avatar")]
2929
public string Avatar { get; set; } //optional
3030
}

JsonFeedNet/JsonFeedHub.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace JsonFeedNet;
22

3-
using Newtonsoft.Json;
3+
using System.Text.Json.Serialization;
44

55
/// <summary>
66
/// Endpoint that can be used to subscribe to real-time notifications of changes to a feed.
@@ -10,12 +10,12 @@ public class JsonFeedHub
1010
/// <summary>
1111
/// The type field describes the protocol used to talk with the hub, such as “rssCloud” or “WebSub.”
1212
/// </summary>
13-
[JsonProperty("type")]
13+
[JsonPropertyName("type")]
1414
public string Type { get; set; } //required
1515

1616
/// <summary>
1717
/// Url of the hub endpoint.
1818
/// </summary>
19-
[JsonProperty("url")]
19+
[JsonPropertyName("url")]
2020
public string Url { get; set; } //required
2121
}

0 commit comments

Comments
 (0)