The following example demonstrates using two Try methods for serialization and deserialization in an Interface called IJsonSerializable. When serializing, the stream position has to be set to 0 to return the json. When deserializing, an empty Model must first be constructed and then deserialized into that instance.
Serialization
SerializableOptions options = new SerializableOptions() { IgnoreReadOnlyProperties = true, IgnoreAdditionalProperties = true };
using Stream stream = new MemoryStream();
Animal model = new Animal();
model.TrySerialize(stream, out long bytesWritten, options: options);
stream.Position = 0;
string json = new StreamReader(stream).ReadToEnd();
Deserialization
using Stream stream = new MemoryStream();
bool ignoreReadOnly = false;
bool ignoreUnknown = false;
string serviceResponse = "{\"latinName\":\"Canis lupus familiaris\",\"weight\":5.5,\"name\":\"Doggo\",\"numberOfLegs\":4}";
SerializableOptions options = new SerializableOptions() { IgnoreReadOnlyProperties = ignoreReadOnly, IgnoreAdditionalProperties = ignoreUnknown };
Animal model = new Animal();
model.TryDeserialize(new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)), out long bytesConsumed, options: options);
The following example demonstrates the NonTry methods for serialization and deserialization. If serialization or deserialization fails, an Exception is bubbled up. When serializing, the stream position has to be set to 0 to return the json. When deserializing, an empty Model must first be constructed and then deserialized into that instance.
Serialization
SerializableOptions options = new SerializableOptions() { IgnoreReadOnlyProperties = true, IgnoreAdditionalProperties = true };
using Stream stream = new MemoryStream();
Animal model = new Animal();
model.Serialize(stream, options: options);
stream.Position = 0;
string roundTrip = new StreamReader(stream).ReadToEnd();
Deserialization
using Stream stream = new MemoryStream();
bool ignoreReadOnly = false;
bool ignoreUnknown = false;
string serviceResponse = "{\"latinName\":\"Canis lupus familiaris\",\"weight\":5.5,\"name\":\"Doggo\",\"numberOfLegs\":4}";
SerializableOptions options = new SerializableOptions() { IgnoreReadOnlyProperties = ignoreReadOnly, IgnoreAdditionalProperties = ignoreUnknown };
Animal model = new Animal();
model.Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)), options: options);
When using protocol methods for advanced handling of RequestContext it is still possible to use the strongly typed models. There is an explicit cast operator that can be used to convert the protocol Response to the strongly typed model. There is also an explicit cast operator that can be used to convert the strongly typed model to the protocol RequestContent.
Serialization
PetStoreClient client = new PetStoreClient(new Uri("http://somewhere.com"), new MockCredential());
DogListProperty dog = new DogListProperty("myPet");
Response response = client.CreatePet("myPet", (RequestContent)dog);
Deserialization
PetStoreClient client = new PetStoreClient(new Uri("http://somewhere.com"), new MockCredential());
Response response = client.GetPet("myPet");
DogListProperty dog = (DogListProperty)response;
Console.WriteLine(dog.IsHungry);
Given that explicit cast does not allow for serialization options we might also consider a static FromResponse
and instance ToRequestContent
methods.
In order to better integrate with the rest of the .NET ecosystem, Azure.Core supports System.Text.Json serialization. The following example demonstrates using System.Text.Json for serialization and deserialization If we go this route the IJsonSerializable interface will only be needed for compile time constraints and can most likely be methodless and renamed to IRehydratable.
One limitation if we go this route is there isn't a clear place to pass in a flag to include additional properties during serialization and deserialization. One solution here is to always have this on by default for public usage and internally turn it off for communication with Azure services.
Serialization
DogListProperty dog = new DogListProperty
{
Name = "Doggo",
IsHungry = false,
Weight = 1.1,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};
//stj example
string json = JsonSerializer.Serialize(dog);
//modelSerializer example
Stream stream = ModelSerializer.Serialize(dog);
Deserialization
string json = "{\"latinName\":\"Animalia\",\"weight\":1.1,\"name\":\"Doggo\",\"isHungry\":false,\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"],\"numberOfLegs\":4}";
//stj example
DogListProperty dog = JsonSerializer.Deserialize<DogListProperty>(json);
//modelSerializer example
DogListProperty dog2 = ModelSerializer.Deserialize<DogListProperty>(json);
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.
SerializableOptions options = new SerializableOptions() { IgnoreReadOnlyProperties = false, IgnoreAdditionalProperties = false };
string serviceResponse =
"{\"latinName\":\"Animalia\",\"weight\":2.3,\"name\":\"Rabbit\",\"isHungry\":false,\"numberOfLegs\":4}";
Animal model = Animal.StaticDeserialize(new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)), options: options);