
committed by
GitHub

No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 252 additions and 220 deletions
-
21.editorconfig
-
57doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml
-
2src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
-
2src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
-
79src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs
-
151src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs
-
1src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
-
157src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs
-
2tools/GenAPI/Microsoft.DotNet.GenAPI/Program.cs
@ -1,28 +1,69 @@ |
|||
<?xml version="1.0"?> |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<docs> |
|||
<members name="SqlJson"> |
|||
<SqlJson> |
|||
<summary>Represents the JSON datatype in SQL Server.</summary> |
|||
</SqlJson> |
|||
<ctor1> |
|||
<summary>Parameterless constructor. Initializes a new instance of the SqlJson class which represents a null JSON value.</summary> |
|||
<summary> |
|||
Construct a new instance of the SqlJson class which represents a null |
|||
JSON value. |
|||
</summary> |
|||
</ctor1> |
|||
<ctor2> |
|||
<param name="jsonString"></param> |
|||
<summary>Takes a <see cref="string"/> as input and initializes a new instance of the SqlJson class.</summary> |
|||
<summary> |
|||
Construct a new instance of the SqlJson class with a serialized JSON |
|||
<see cref="string"/>. The string is validated by parsing it with |
|||
<see cref="System.Text.Json.JsonDocument"/>. |
|||
</summary> |
|||
<param name="jsonString"> |
|||
The serialized JSON string to use, or null. |
|||
</param> |
|||
<throw> |
|||
<exception cref="System.Text.Json.JsonException"> |
|||
If the given string is not valid JSON. |
|||
</exception> |
|||
</throw> |
|||
</ctor2> |
|||
<ctor3> |
|||
<param name="jsonDoc"></param> |
|||
<summary>Takes a <see cref="System.Text.Json.JsonDocument"/> as input and initializes a new instance of the SqlJson class.</summary> |
|||
<summary> |
|||
Construct a new instance of the SqlJson class with a |
|||
<see cref="System.Text.Json.JsonDocument"/>. The serialized JSON string |
|||
from the document is saved. |
|||
</summary> |
|||
<param name="jsonDoc"> |
|||
The document to use, or null. |
|||
</param> |
|||
<throw> |
|||
<exception cref="System.ObjectDisposedException"> |
|||
If the given document has been disposed of. |
|||
</exception> |
|||
</throw> |
|||
</ctor3> |
|||
<IsNull> |
|||
<inheritdoc/> |
|||
</IsNull> |
|||
<Null> |
|||
<summary>Represents a null instance of the <see cref="SqlJson"/> type.</summary> |
|||
<summary> |
|||
Represents a null instance of the <see cref="SqlJson"/> type. This |
|||
instance is equivalent to calling the parameterless constructor, or |
|||
calling the other constructors with a null value. |
|||
</summary> |
|||
</Null> |
|||
<Value> |
|||
<summary>Gets the string representation of the Json content of this <see cref="SqlJson" /> instance.</summary> |
|||
<summary> |
|||
Gets the serialized JSON string of this <see cref="SqlJson" /> instance. |
|||
</summary> |
|||
<throw> |
|||
<exception cref="System.Data.SqlTypes.SqlNullValueException"> |
|||
If the JSON value is null. |
|||
</exception> |
|||
</throw> |
|||
</Value> |
|||
<ToString> |
|||
<summary> |
|||
Returns the serialized JSON string, or null. |
|||
</summary> |
|||
</ToString> |
|||
</members> |
|||
</docs> |
@ -1,151 +0,0 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
using System; |
|||
using System.Data.SqlTypes; |
|||
using System.Linq; |
|||
using System.Text.Json; |
|||
using Microsoft.Data.SqlTypes; |
|||
using Xunit; |
|||
|
|||
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.Json |
|||
{ |
|||
|
|||
public class SqlJsonTest |
|||
{ |
|||
[Fact] |
|||
public void SqlJsonTest_Null() |
|||
{ |
|||
SqlJson json = new(); |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
|
|||
} |
|||
|
|||
[Fact] |
|||
public void SqlJsonTest_NullString() |
|||
{ |
|||
string nullString = null; |
|||
SqlJson json = new(nullString); |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SqlJsonTest_NullJsonDocument() |
|||
{ |
|||
JsonDocument doc = null; |
|||
SqlJson json = new(doc); |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SqlJsonTest_String() |
|||
{ |
|||
SqlJson json = new("{\"key\":\"value\"}"); |
|||
Assert.False(json.IsNull); |
|||
Assert.Equal("{\"key\":\"value\"}", json.Value); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SqlJsonTest_BadString() |
|||
{ |
|||
Assert.ThrowsAny<JsonException>(()=> new SqlJson("{\"key\":\"value\"")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SqlJsonTest_JsonDocument() |
|||
{ |
|||
JsonDocument doc = GenerateRandomJson(); |
|||
SqlJson json = new(doc); |
|||
Assert.False(json.IsNull); |
|||
|
|||
var outputDocument = JsonDocument.Parse(json.Value); |
|||
Assert.True(JsonElementsAreEqual(doc.RootElement, outputDocument.RootElement)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SqlJsonTest_NullProperty() |
|||
{ |
|||
SqlJson json = SqlJson.Null; |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
} |
|||
|
|||
static JsonDocument GenerateRandomJson() |
|||
{ |
|||
var random = new Random(); |
|||
|
|||
var jsonObject = new |
|||
{ |
|||
id = random.Next(1, 1000), |
|||
name = $"Name{random.Next(1, 100)}", |
|||
isActive = random.Next(0, 2) == 1, |
|||
createdDate = DateTime.Now.AddDays(-random.Next(1, 100)).ToString("yyyy-MM-ddTHH:mm:ssZ"), |
|||
scores = new int[] { random.Next(1, 100), random.Next(1, 100), random.Next(1, 100) }, |
|||
details = new |
|||
{ |
|||
age = random.Next(18, 60), |
|||
city = $"City{random.Next(1, 100)}" |
|||
} |
|||
}; |
|||
|
|||
string jsonString = JsonSerializer.Serialize(jsonObject, new JsonSerializerOptions { WriteIndented = true }); |
|||
return JsonDocument.Parse(jsonString); |
|||
} |
|||
|
|||
static bool JsonElementsAreEqual(JsonElement element1, JsonElement element2) |
|||
{ |
|||
if (element1.ValueKind != element2.ValueKind) |
|||
return false; |
|||
|
|||
switch (element1.ValueKind) |
|||
{ |
|||
case JsonValueKind.Object: |
|||
{ |
|||
JsonElement.ObjectEnumerator obj1 = element1.EnumerateObject(); |
|||
JsonElement.ObjectEnumerator obj2 = element2.EnumerateObject(); |
|||
var dict1 = obj1.ToDictionary(p => p.Name, p => p.Value); |
|||
var dict2 = obj2.ToDictionary(p => p.Name, p => p.Value); |
|||
|
|||
if (dict1.Count != dict2.Count) |
|||
return false; |
|||
|
|||
foreach (var kvp in dict1) |
|||
{ |
|||
if (!dict2.TryGetValue(kvp.Key, out var value2)) |
|||
return false; |
|||
|
|||
if (!JsonElementsAreEqual(kvp.Value, value2)) |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
case JsonValueKind.Array: |
|||
{ |
|||
var array1 = element1.EnumerateArray(); |
|||
var array2 = element2.EnumerateArray(); |
|||
|
|||
if (array1.Count() != array2.Count()) |
|||
return false; |
|||
|
|||
return array1.Zip(array2, (e1, e2) => JsonElementsAreEqual(e1, e2)).All(equal => equal); |
|||
} |
|||
case JsonValueKind.String: |
|||
return element1.GetString() == element2.GetString(); |
|||
case JsonValueKind.Number: |
|||
return element1.GetDecimal() == element2.GetDecimal(); |
|||
case JsonValueKind.True: |
|||
case JsonValueKind.False: |
|||
return element1.GetBoolean() == element2.GetBoolean(); |
|||
case JsonValueKind.Null: |
|||
return true; |
|||
default: |
|||
throw new NotSupportedException($"Unsupported JsonValueKind: {element1.ValueKind}"); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,157 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
#nullable enable |
|||
|
|||
using System; |
|||
using System.Data.SqlTypes; |
|||
using System.Text.Json; |
|||
using Microsoft.Data.SqlTypes; |
|||
using Xunit; |
|||
|
|||
namespace Microsoft.Data.SqlClient.UnitTests; |
|||
|
|||
public class SqlJsonTest |
|||
{ |
|||
#region Private Fields
|
|||
|
|||
private static readonly JsonSerializerOptions _jsonOptions = new() |
|||
{ |
|||
WriteIndented = true |
|||
}; |
|||
|
|||
#endregion
|
|||
|
|||
#region Tests
|
|||
|
|||
// Test the static Null property.
|
|||
[Fact] |
|||
public void StaticNull() |
|||
{ |
|||
Assert.True(SqlJson.Null.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => SqlJson.Null.Value); |
|||
Assert.Null(SqlJson.Null.ToString()); |
|||
} |
|||
|
|||
// Test the constructor that takes no arguments.
|
|||
[Fact] |
|||
public void Constructor_NoArgs() |
|||
{ |
|||
SqlJson json = new(); |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
Assert.Null(json.ToString()); |
|||
} |
|||
|
|||
// Test the constructors that take a nullable string.
|
|||
[Fact] |
|||
public void Constructor_String_Null() |
|||
{ |
|||
const string? value = null; |
|||
SqlJson json = new(value); |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
Assert.Null(json.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Constructor_String_NotNull() |
|||
{ |
|||
const string value = "{\"key\":\"value\"}"; |
|||
SqlJson json = new(value); |
|||
Assert.False(json.IsNull); |
|||
Assert.Equal(value, json.Value); |
|||
Assert.Equal(value, json.ToString()); |
|||
} |
|||
|
|||
[Theory] |
|||
// Non-string key.
|
|||
[InlineData("{key:\"value\"}")] |
|||
// Invalid value type.
|
|||
[InlineData("{\"key\":value}")] |
|||
// Missing closing brace.
|
|||
[InlineData("{\"key\":\"value\"")] |
|||
// Trailing comma.
|
|||
[InlineData("{\"key\":\"value\",}")] |
|||
// Comment in JSON.
|
|||
[InlineData("// comment {\"key\":\"value\"}")] |
|||
public void Constructor_String_Invalid(string invalid) |
|||
{ |
|||
Assert.ThrowsAny<JsonException>(() => new SqlJson(invalid)); |
|||
} |
|||
|
|||
// Test the constructor that takes a nullable JsonDocument.
|
|||
[Fact] |
|||
public void Constructor_JsonDocument_Null() |
|||
{ |
|||
const JsonDocument? doc = null; |
|||
SqlJson json = new(doc); |
|||
Assert.True(json.IsNull); |
|||
Assert.Throws<SqlNullValueException>(() => json.Value); |
|||
Assert.Null(json.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Constructor_JsonDocument_NotNull() |
|||
{ |
|||
using JsonDocument doc = GenerateRandomJson(); |
|||
SqlJson json = new(doc); |
|||
Assert.False(json.IsNull); |
|||
Assert.Equal(doc.RootElement.GetRawText(), json.Value); |
|||
Assert.Equal(doc.RootElement.GetRawText(), json.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Constructor_JsonDocument_Disposed() |
|||
{ |
|||
using JsonDocument doc = GenerateRandomJson(); |
|||
doc.Dispose(); |
|||
Assert.Throws<ObjectDisposedException>(() => new SqlJson(doc)); |
|||
} |
|||
|
|||
// IsNull, Value, and ToString() are covered by the above tests.
|
|||
|
|||
// Test that the Value can be round-tripped through a JsonDocument.
|
|||
//
|
|||
// JsonElement.DeepEquals() is only available in .NET 9.0 and later.
|
|||
#if NET9_0_OR_GREATER
|
|||
[Fact] |
|||
public void RoundTrip() |
|||
{ |
|||
using JsonDocument doc = GenerateRandomJson(); |
|||
SqlJson json = new(doc); |
|||
|
|||
using var outputDocument = JsonDocument.Parse(json.Value); |
|||
Assert.True(JsonElement.DeepEquals( |
|||
doc.RootElement, outputDocument.RootElement)); |
|||
} |
|||
#endif
|
|||
|
|||
#endregion
|
|||
|
|||
#region Helpers
|
|||
|
|||
private static JsonDocument GenerateRandomJson() |
|||
{ |
|||
Random random = new(); |
|||
|
|||
object jsonObject = new |
|||
{ |
|||
id = random.Next(1, 1000), |
|||
name = $"Name{random.Next(1, 100)}", |
|||
isActive = random.Next(0, 2) == 1, |
|||
createdDate = DateTime.Now.AddDays(-random.Next(1, 100)).ToString("yyyy-MM-ddTHH:mm:ssZ"), |
|||
scores = new int[] { random.Next(1, 100), random.Next(1, 100), random.Next(1, 100) }, |
|||
details = new |
|||
{ |
|||
age = random.Next(18, 60), |
|||
city = $"City{random.Next(1, 100)}" |
|||
} |
|||
}; |
|||
|
|||
return JsonSerializer.SerializeToDocument(jsonObject, _jsonOptions); |
|||
} |
|||
|
|||
#endregion
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue