Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net461.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ public SerializableOptions() { }
public bool IgnoreAdditionalProperties { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool PrettyPrint { get { throw null; } set { } }
public Azure.Core.Serialization.ObjectSerializer? Serializer { get { throw null; } set { } }
}
}
namespace Azure.Messaging
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net5.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ public SerializableOptions() { }
public bool IgnoreAdditionalProperties { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool PrettyPrint { get { throw null; } set { } }
public Azure.Core.Serialization.ObjectSerializer? Serializer { get { throw null; } set { } }
}
}
namespace Azure.Messaging
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net6.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ public SerializableOptions() { }
public bool IgnoreAdditionalProperties { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool PrettyPrint { get { throw null; } set { } }
public Azure.Core.Serialization.ObjectSerializer? Serializer { get { throw null; } set { } }
}
}
namespace Azure.Messaging
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ public SerializableOptions() { }
public bool IgnoreAdditionalProperties { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool PrettyPrint { get { throw null; } set { } }
public Azure.Core.Serialization.ObjectSerializer? Serializer { get { throw null; } set { } }
}
}
namespace Azure.Messaging
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ public SerializableOptions() { }
public bool IgnoreAdditionalProperties { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool PrettyPrint { get { throw null; } set { } }
public Azure.Core.Serialization.ObjectSerializer? Serializer { get { throw null; } set { } }
}
}
namespace Azure.Messaging
Expand Down
59 changes: 52 additions & 7 deletions sdk/core/Azure.Core/samples/Serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,8 @@ DogListProperty dog = new DogListProperty
FoodConsumed = { "kibble", "egg", "peanut butter" },
};

//stj example
//STJ example
string json = JsonSerializer.Serialize(dog);

//modelSerializer example
Stream stream = ModelSerializer.Serialize(dog);
```

Deserialization
Expand All @@ -113,9 +110,6 @@ string json = "{\"latinName\":\"Animalia\",\"weight\":1.1,\"name\":\"Doggo\",\"i

//stj example
DogListProperty dog = JsonSerializer.Deserialize<DogListProperty>(json);

//modelSerializer example
DogListProperty dog2 = ModelSerializer.Deserialize<DogListProperty>(json);
```

## Using static deserializer
Expand All @@ -130,4 +124,55 @@ string serviceResponse =
Animal model = Animal.StaticDeserialize(new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)), options: options);
```

## Using ModelSerializer

Serialize would use the Try/Do examples from above. We would use Interface form the Serializable but potentially have static method for Deserialize.
When using Static Deserialize, an empty Model does not have to be created first as we can deserialize directly into a new instance.

Serialization
```C# Snippet:ModelSerializer_Serialize
DogListProperty dog = new DogListProperty
{
Name = "Doggo",
IsHungry = true,
Weight = 1.1,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};

Stream stream = ModelSerializer.Serialize(dog);
```

Deserialization
```C# Snippet:ModelSerializer_Deserialize
string json = @"[{""LatinName"":""Animalia"",""Weight"":1.1,""Name"":""Doggo"",""IsHungry"":false,""FoodConsumed"":[""kibble"",""egg"",""peanut butter""],""NumberOfLegs"":4}]";

DogListProperty dog = ModelSerializer.Deserialize<DogListProperty>(json);
```

## Using ModelSerializer for NewtonSoftJson
By using the ModelSerializer class, a new instance of Dog does not need to be created before calling Deserialize. Also added ObjectSerializer to Options class so different kinds of Serializers can be used.

Serialization
```C# Snippet:NewtonSoft_Serialize
DogListProperty dog = new DogListProperty
{
Name = "Doggo",
IsHungry = true,
Weight = 1.1,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};
SerializableOptions options = new SerializableOptions();
options.Serializer = new NewtonsoftJsonObjectSerializer();

Stream stream = ModelSerializer.Serialize(dog, options);
```

Deserialization

```C# Snippet:NewtonSoft_Deserialize
SerializableOptions options = new SerializableOptions();
options.Serializer = new NewtonsoftJsonObjectSerializer();
string json = @"[{""LatinName"":""Animalia"",""Weight"":1.1,""Name"":""Doggo"",""IsHungry"":false,""FoodConsumed"":[""kibble"",""egg"",""peanut butter""],""NumberOfLegs"":4}]";

DogListProperty dog = ModelSerializer.Deserialize<DogListProperty>(json, options);
```
28 changes: 27 additions & 1 deletion sdk/core/Azure.Core/src/Serialization/ModelSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.IO;

namespace Azure.Core.Serialization
Expand All @@ -11,14 +12,21 @@ namespace Azure.Core.Serialization
public static class ModelSerializer
{
/// <summary>
/// Serailize a model.
/// Serialize a model.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="model"></param>
/// <param name="options"></param>
/// <returns></returns>
public static Stream Serialize<T>(T model, SerializableOptions? options = default) where T : IJsonSerializable, new()
{
// if options.Serializer is set
if (options != null && options.Serializer != null)
{
System.BinaryData data = options.Serializer.Serialize(model);
return data.ToStream();
}

IJsonSerializable serializable = (model ??= new T()) as IJsonSerializable;
Stream stream = new MemoryStream();
serializable.Serialize(stream, options);
Expand All @@ -34,6 +42,15 @@ public static class ModelSerializer
/// <returns></returns>
public static T Deserialize<T>(Stream stream, SerializableOptions? options = default) where T : IJsonSerializable, new()
{
if (options != null && options.Serializer != null)
{
var obj = options.Serializer.Deserialize(stream, typeof(T), default);
if (obj is null)
throw new InvalidOperationException();
else
return (T)obj;
}

IJsonSerializable serializable = new T();
serializable.Deserialize(stream, options);
return (T)serializable;
Expand All @@ -53,6 +70,15 @@ public static class ModelSerializer
writer.Write(json);
stream.Position = 0;

if (options != null && options.Serializer != null)
{
var obj = options.Serializer.Deserialize(stream, typeof(T), default);
if (obj is null)
throw new InvalidOperationException();
else
return (T)obj;
}

IJsonSerializable serializable = new T();
serializable.Deserialize(stream, options);
return (T)serializable;
Expand Down
5 changes: 5 additions & 0 deletions sdk/core/Azure.Core/src/Serialization/SerializableOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ public class SerializableOptions
/// Bool that determines if Json will be PrettyPrinted. Default is false.
/// </summary>
public bool PrettyPrint { get; set; }

/// <summary>
/// todo
/// </summary>
public ObjectSerializer? Serializer { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.Core.Serialization;
using NUnit.Framework;
using Newtonsoft.Json;

namespace Azure.Core.Tests.ModelSerializationTests
{
internal class NewtonSoftTests
{
private readonly SerializableOptions _wireOptions = new SerializableOptions { IgnoreReadOnlyProperties = false };
private readonly SerializableOptions _objectOptions = new SerializableOptions();

[TestCase(true)]
[TestCase(false)]
public void CanRoundTripFutureVersionWithoutLoss(bool ignoreReadOnly)
{
Stream stream = new MemoryStream();
string serviceResponse =
"{\"latinName\":\"Animalia\",\"weight\":2.3,\"name\":\"Rabbit\",\"isHungry\":false}";

StringBuilder expectedSerialized = new StringBuilder("{");
expectedSerialized.Append("\"IsHungry\":false,");
expectedSerialized.Append("\"Weight\":2.3,");
if (!ignoreReadOnly)
{
expectedSerialized.Append("\"LatinName\":\"Animalia\",");
}
expectedSerialized.Append("\"Name\":\"Rabbit\"");
expectedSerialized.Append("}");
var expectedSerializedString = expectedSerialized.ToString();

SerializableOptions options = new SerializableOptions() { IgnoreReadOnlyProperties = ignoreReadOnly };

if (ignoreReadOnly)
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new IgnoreReadOnlyPropertiesResolver()
};
options.Serializer = new NewtonsoftJsonObjectSerializer(settings);
}
else
options.Serializer = new NewtonsoftJsonObjectSerializer();

var model = ModelSerializer.Deserialize<Animal>(new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)), options: options);

if (!ignoreReadOnly)
{
Assert.That(model.LatinName, Is.EqualTo("Animalia"));
}
Assert.That(model.Name, Is.EqualTo("Rabbit"));
Assert.IsFalse(model.IsHungry);

#if NET6_0_OR_GREATER
Assert.That(model.Weight, Is.EqualTo(2.3));
#endif

stream = ModelSerializer.Serialize<Animal>(model, options);
stream.Position = 0;
string roundTrip = new StreamReader(stream).ReadToEnd();

#if NET6_0_OR_GREATER
Assert.That(roundTrip, Is.EqualTo(expectedSerializedString));
#endif

var model2 = ModelSerializer.Deserialize<Animal>(new MemoryStream(Encoding.UTF8.GetBytes(roundTrip)), options: options);
VerifyModels.CheckAnimals(model, model2, options);
}

// Generate a class that implements the NewtonSoft default contract resolver so that ReadOnly properties are not serialized
// This is used to verify that the ReadOnly properties are not serialized when IgnoreReadOnlyProperties is set to true
private class IgnoreReadOnlyPropertiesResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
Newtonsoft.Json.Serialization.JsonProperty property = base.CreateProperty(member, memberSerialization);

if (!property.Writable)
{
property.ShouldSerialize = obj => false;
}

return property;
}
}
}
}
73 changes: 61 additions & 12 deletions sdk/core/Azure.Core/tests/samples/SerializationSamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Core.Experimental.Tests;
using Azure.Core.Serialization;
using Azure.Core.TestFramework;
using Azure.Core.Tests.ModelSerializationTests;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using NUnit.Framework;

namespace Azure.Core.Samples
Expand Down Expand Up @@ -116,11 +111,8 @@ public void StjSerialize()
FoodConsumed = { "kibble", "egg", "peanut butter" },
};

//stj example
//STJ example
string json = JsonSerializer.Serialize(dog);

//modelSerializer example
Stream stream = ModelSerializer.Serialize(dog);
#endregion
}

Expand All @@ -133,9 +125,6 @@ public void StjDeserialize()

//stj example
DogListProperty dog = JsonSerializer.Deserialize<DogListProperty>(json);

//modelSerializer example
DogListProperty dog2 = ModelSerializer.Deserialize<DogListProperty>(json);
#endregion
}

Expand All @@ -151,5 +140,65 @@ public void StaticDeserialize()
Animal model = Animal.StaticDeserialize(new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)), options: options);
#endregion
}

[Test]
[Ignore("Only verifying that the sample builds")]
public void NewtonSoftSerialize()
{
#region Snippet:NewtonSoft_Serialize
DogListProperty dog = new DogListProperty
{
Name = "Doggo",
IsHungry = true,
Weight = 1.1,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};
SerializableOptions options = new SerializableOptions();
options.Serializer = new NewtonsoftJsonObjectSerializer();

Stream stream = ModelSerializer.Serialize(dog, options);
#endregion
}

[Test]
[Ignore("Only verifying that the sample builds")]
public void NewtonSoftDeserialize()
{
#region Snippet:NewtonSoft_Deserialize
SerializableOptions options = new SerializableOptions();
options.Serializer = new NewtonsoftJsonObjectSerializer();
string json = @"[{""LatinName"":""Animalia"",""Weight"":1.1,""Name"":""Doggo"",""IsHungry"":false,""FoodConsumed"":[""kibble"",""egg"",""peanut butter""],""NumberOfLegs"":4}]";

DogListProperty dog = ModelSerializer.Deserialize<DogListProperty>(json, options);
#endregion
}

[Test]
[Ignore("Only verifying that the sample builds")]
public void ModelSerializerSerialize()
{
#region Snippet:ModelSerializer_Serialize
DogListProperty dog = new DogListProperty
{
Name = "Doggo",
IsHungry = true,
Weight = 1.1,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};

Stream stream = ModelSerializer.Serialize(dog);
#endregion
}

[Test]
[Ignore("Only verifying that the sample builds")]
public void ModelSerializerDeserialize()
{
#region Snippet:ModelSerializer_Deserialize
string json = @"[{""LatinName"":""Animalia"",""Weight"":1.1,""Name"":""Doggo"",""IsHungry"":false,""FoodConsumed"":[""kibble"",""egg"",""peanut butter""],""NumberOfLegs"":4}]";

DogListProperty dog = ModelSerializer.Deserialize<DogListProperty>(json);
#endregion
}
}
}