Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 398dc98

Browse files
authored
Use Uri.OriginalString when serializing (#39015)
* Use Uri.OriginalString when serializing We want to be lossless if possible when round tripping (outside of escaping). To do so we need to write OriginalString instead of using ToString(). Removing pending issue reference as it was closed (we won't be adding a Uri parser to the reader). Add user reported scenario. * Address feedback
1 parent ed5b558 commit 398dc98

5 files changed

Lines changed: 98 additions & 3 deletions

File tree

src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUri.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ internal sealed class JsonConverterUri : JsonConverter<Uri>
88
{
99
public override Uri Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1010
{
11-
// TODO: use reader.GetUri() when https://github.com/dotnet/corefx/issues/38647 is implemented.
1211
string uriString = reader.GetString();
1312
if (Uri.TryCreate(uriString, UriKind.RelativeOrAbsolute, out Uri value))
1413
{
@@ -21,8 +20,7 @@ public override Uri Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeri
2120

2221
public override void Write(Utf8JsonWriter writer, Uri value, JsonSerializerOptions options)
2322
{
24-
// TODO: remove preprocessing when https://github.com/dotnet/corefx/issues/38647 is implemented.
25-
writer.WriteStringValue(value.ToString());
23+
writer.WriteStringValue(value.OriginalString);
2624
}
2725
}
2826
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Text.Json.Serialization;
6+
using Xunit;
7+
8+
namespace System.Text.Json.Tests.Serialization
9+
{
10+
/// <summary>
11+
/// Catch-all location for combined read scenarios (user reported and otherwise) that aren't
12+
/// specific to one serialization feature.
13+
/// </summary>
14+
public class ReadScenarioTests
15+
{
16+
[Fact]
17+
public void StringEnumUriAndCustomDateTimeConverter()
18+
{
19+
// Validating a scenario reported with https://github.com/dotnet/corefx/issues/38568.
20+
// Our DateTime parsing is ISO 8601 strict, more flexible parsing is possible by
21+
// writing a simple converter. String based enum parsing is handled by registering
22+
// a custom built-in parser (JsonStringEnumConverter). Uri is handled implicitly.
23+
24+
string json =
25+
@"{" +
26+
@"""picture"": ""http://placehold.it/32x32""," +
27+
@"""eyeColor"": ""Brown""," +
28+
@"""registered"": ""2015-05-30T01:50:21 -01:00""" +
29+
@"}";
30+
31+
JsonSerializerOptions options = new JsonSerializerOptions
32+
{
33+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
34+
Converters =
35+
{
36+
new JsonStringEnumConverter(),
37+
new Types38568.MyDateTimeConverter()
38+
}
39+
};
40+
41+
Types38568.Model model = JsonSerializer.Deserialize<Types38568.Model>(json, options);
42+
Assert.Equal(Types38568.Color.Brown, model.EyeColor);
43+
Assert.Equal(@"http://placehold.it/32x32", model.Picture.OriginalString);
44+
Assert.Equal(DateTime.Parse("2015-05-30T01:50:21 -01:00"), model.Registered);
45+
}
46+
47+
public class Types38568
48+
{
49+
// The built-in DateTime parser is stricter than DateTime.Parse.
50+
public class MyDateTimeConverter : JsonConverter<DateTime>
51+
{
52+
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
53+
=> DateTime.Parse(reader.GetString());
54+
55+
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
56+
=> writer.WriteStringValue(value.ToString("O"));
57+
}
58+
59+
public sealed class Model
60+
{
61+
public Color EyeColor { get; set; }
62+
public Uri Picture { get; set; }
63+
public DateTime Registered { get; set; }
64+
}
65+
66+
public enum Color
67+
{
68+
Blue,
69+
Green,
70+
Brown
71+
}
72+
}
73+
}
74+
}

src/System.Text.Json/tests/Serialization/Value.ReadTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,15 @@ public static void ReadPrimitiveUri()
291291
{
292292
Uri uri = JsonSerializer.Deserialize<Uri>(@"""https://domain/path""");
293293
Assert.Equal("https:\u002f\u002fdomain\u002fpath", uri.ToString());
294+
Assert.Equal("https://domain/path", uri.OriginalString);
295+
296+
uri = JsonSerializer.Deserialize<Uri>(@"""https:\u002f\u002fdomain\u002fpath""");
297+
Assert.Equal("https:\u002f\u002fdomain\u002fpath", uri.ToString());
298+
Assert.Equal("https://domain/path", uri.OriginalString);
294299

295300
uri = JsonSerializer.Deserialize<Uri>(@"""~/path""");
296301
Assert.Equal("~/path", uri.ToString());
302+
Assert.Equal("~/path", uri.OriginalString);
297303
}
298304

299305
private static int SingleToInt32Bits(float value)

src/System.Text.Json/tests/Serialization/Value.WriteTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ public static void WritePrimitives()
6262
Uri.TryCreate("~/path", UriKind.RelativeOrAbsolute, out Uri uri);
6363
Assert.Equal(@"""~\u002fpath""", JsonSerializer.Serialize(uri));
6464
}
65+
66+
// The next two scenarios validate that we're NOT using Uri.ToString() for serializing Uri. The serializer
67+
// will escape backslashes and ampersands, but otherwise should be the same as the output of Uri.OriginalString.
68+
69+
{
70+
// ToString would collapse the relative segment
71+
Uri uri = new Uri("http://a/b/../c");
72+
Assert.Equal(@"""http:\u002f\u002fa\u002fb\u002f..\u002fc""", JsonSerializer.Serialize(uri));
73+
}
74+
75+
{
76+
// "%20" gets turned into a space by Uri.ToString()
77+
// https://coding.abel.nu/2014/10/beware-of-uri-tostring/
78+
Uri uri = new Uri("http://localhost?p1=Value&p2=A%20B%26p3%3DFooled!");
79+
Assert.Equal(@"""http:\u002f\u002flocalhost?p1=Value\u0026p2=A%20B%26p3%3DFooled!""", JsonSerializer.Serialize(uri));
80+
}
6581
}
6682
}
6783
}

src/System.Text.Json/tests/System.Text.Json.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
<Compile Include="Serialization\TestClasses.SimpleTestClassWithSimpleObject.cs" />
8080
<Compile Include="Serialization\TestClasses.SimpleTestStruct.cs" />
8181
<Compile Include="Serialization\TestData.cs" />
82+
<Compile Include="Serialization\ReadScenarioTests.cs" />
8283
<Compile Include="Serialization\Value.ReadTests.cs" />
8384
<Compile Include="Serialization\Value.ReadTests.GenericCollections.cs" />
8485
<Compile Include="Serialization\Value.ReadTests.ImmutableCollections.cs" />

0 commit comments

Comments
 (0)