diff --git a/CHANGELOG.md b/CHANGELOG.md index 85927d47..9a4bb347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.9.12] - 2024-07-30 + +- Fix non IParasable object serialization. +- Add basic support for serializing dictionary values in AdditionalData. + ## [1.9.11] - 2024-07-22 - Obsoletes custom decompression handler in favor of native client capabilities. diff --git a/Directory.Build.props b/Directory.Build.props index 1d7bd68b..97585040 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 1.9.11 + 1.9.12 false diff --git a/src/serialization/json/JsonSerializationWriter.cs b/src/serialization/json/JsonSerializationWriter.cs index 29d2697c..23c41657 100644 --- a/src/serialization/json/JsonSerializationWriter.cs +++ b/src/serialization/json/JsonSerializationWriter.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------------ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; @@ -344,7 +345,7 @@ public void WriteCollectionOfObjectValues(string? key, IEnumerable? values /// The key to be used for the written value. May be null. /// The enum values to be written. #if NET5_0_OR_GREATER - public void WriteCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]T>(string? key, IEnumerable? values) where T : struct, Enum + public void WriteCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(string? key, IEnumerable? values) where T : struct, Enum #else public void WriteCollectionOfEnumValues(string? key, IEnumerable? values) where T : struct, Enum #endif @@ -360,6 +361,32 @@ public void WriteCollectionOfEnumValues(string? key, IEnumerable? values) } } /// + /// Writes the specified dictionary to the stream with an optional given key. + /// + /// The key to be used for the written value. May be null. + /// The dictionary of values to be written. +#if NET5_0_OR_GREATER + private void WriteDictionaryValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(string? key, T values) where T : IDictionary +#else + private void WriteDictionaryValue(string? key, T values) where T : IDictionary +#endif + { + if(values != null) + { + if(!string.IsNullOrEmpty(key)) + writer.WritePropertyName(key!); + + writer.WriteStartObject(); + foreach(DictionaryEntry entry in values) + { + if(entry.Key is not string keyStr) + throw new InvalidOperationException($"Error serializing dictionary value with key {key}, only string keyed dictionaries are supported."); + WriteAnyValue(keyStr, entry.Value); + } + writer.WriteEndObject(); + } + } + /// /// Writes the specified byte array as a base64 string to the stream with an optional given key. /// /// The key to be used for the written value. May be null. @@ -433,7 +460,7 @@ public void WriteAdditionalData(IDictionary value) } #if NET5_0_OR_GREATER - private void WriteNonParsableObjectValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string? key,T value) + private void WriteNonParsableObjectValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string? key, T value) #else private void WriteNonParsableObjectValue(string? key, T value) #endif @@ -444,7 +471,7 @@ private void WriteNonParsableObjectValue(string? key, T value) if(value == null) writer.WriteNullValue(); else - foreach(var oProp in typeof(T).GetProperties()) + foreach(var oProp in value.GetType().GetProperties()) WriteAnyValue(oProp.Name, oProp.GetValue(value)); writer.WriteEndObject(); } @@ -512,6 +539,9 @@ private void WriteAnyValue(string? key, T value) writer.WritePropertyName(key!); jsonElement.WriteTo(writer); break; + case IDictionary dictionary: + WriteDictionaryValue(key, dictionary); + break; case object o: WriteNonParsableObjectValue(key, o); break; @@ -519,7 +549,7 @@ private void WriteAnyValue(string? key, T value) WriteNullValue(key); break; default: - throw new InvalidOperationException($"error serialization additional data value with key {key}, unknown type {value?.GetType()}"); + throw new InvalidOperationException($"Error serializing additional data value with key {key}, unknown type {value?.GetType()}"); } } diff --git a/tests/serialization/json/JsonSerializationWriterTests.cs b/tests/serialization/json/JsonSerializationWriterTests.cs index ed9a4b2c..252e5eba 100644 --- a/tests/serialization/json/JsonSerializationWriterTests.cs +++ b/tests/serialization/json/JsonSerializationWriterTests.cs @@ -41,8 +41,12 @@ public void WritesSampleObjectValue() {"businessPhones", new List() {"+1 412 555 0109"}}, // write collection of primitives value {"endDateTime", new DateTime(2023,03,14,0,0,0,DateTimeKind.Utc) }, // ensure the DateTime doesn't crash {"manager", new TestEntity{Id = "48d31887-5fad-4d73-a9f5-3c356e68a038"}}, // write nested object value + {"anonymousObject", new {Value1 = true, Value2 = "", Value3 = new List{ "Value3.1", "Value3.2"}}}, // write nested object value + {"dictionaryString", new Dictionary{{"91bbe8e2-09b2-482b-a90e-00f8d7e81636", "b7992f48-a51b-41a1-ace5-4cebb7f111d0"}, { "ed64c116-2776-4012-94d1-a348b9d241bd", "55e1b4d0-2959-4c71-89b5-385ba5338a1c" }, }}, // write a Dictionary + {"dictionaryTestEntity", new Dictionary{{ "dd476fc9-7e97-4a4e-8d40-6c3de7432eb3", new TestEntity { Id = "dd476fc9-7e97-4a4e-8d40-6c3de7432eb3" } }, { "ffa5c351-7cf5-43df-9b55-e12455cf6eb2", new TestEntity { Id = "ffa5c351-7cf5-43df-9b55-e12455cf6eb2" } }, }}, // write a Dictionary } }; + using var jsonSerializerWriter = new JsonSerializationWriter(); // Act jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity); @@ -65,7 +69,10 @@ public void WritesSampleObjectValue() "\"weightInKgs\":51.80," + "\"businessPhones\":[\"\\u002B1 412 555 0109\"]," + "\"endDateTime\":\"2023-03-14T00:00:00+00:00\"," + - "\"manager\":{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"}" + + "\"manager\":{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"}," + + "\"anonymousObject\":{\"Value1\":true,\"Value2\":\"\",\"Value3\":[\"Value3.1\",\"Value3.2\"]}," + + "\"dictionaryString\":{\"91bbe8e2-09b2-482b-a90e-00f8d7e81636\":\"b7992f48-a51b-41a1-ace5-4cebb7f111d0\",\"ed64c116-2776-4012-94d1-a348b9d241bd\":\"55e1b4d0-2959-4c71-89b5-385ba5338a1c\"}," + + "\"dictionaryTestEntity\":{\"dd476fc9-7e97-4a4e-8d40-6c3de7432eb3\":{\"id\":\"dd476fc9-7e97-4a4e-8d40-6c3de7432eb3\"},\"ffa5c351-7cf5-43df-9b55-e12455cf6eb2\":{\"id\":\"ffa5c351-7cf5-43df-9b55-e12455cf6eb2\"}}" + "}"; Assert.Equal(expectedString, serializedJsonString); } @@ -161,6 +168,24 @@ public void WritesSampleCollectionOfObjectValues() Assert.Equal(expectedString, serializedJsonString); } + [Fact] + public void DoesntWriteUnsupportedTypes_NonStringKeyedDictionary() + { + // Arrange + var testEntity = new TestEntity() + { + AdditionalData = new Dictionary + { + {"nonStringKeyedDictionary", new Dictionary{{ 1, "one" }, { 2, "two" }}} + } + }; + + using var jsonSerializerWriter = new JsonSerializationWriter(); + // Act & Assert + var exception = Assert.Throws(() => jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity)); + Assert.Equal("Error serializing dictionary value with key nonStringKeyedDictionary, only string keyed dictionaries are supported.", exception.Message); + } + [Fact] public void WritesEnumValuesAsCamelCasedIfNotEscaped() {