Skip to content

Commit d5fc696

Browse files
authored
Merge pull request #122 from AzureCosmosDB/feature/type-metadata-passthrough
Fixes #97 for outputting JSON metadata properties on child objects
2 parents 537d732 + 447b058 commit d5fc696

File tree

4 files changed

+126
-10
lines changed

4 files changed

+126
-10
lines changed

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosDictionaryDataItem.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Cosmos.DataTransfer.Interfaces;
2+
using Newtonsoft.Json;
23
using Newtonsoft.Json.Linq;
34

45
namespace Cosmos.DataTransfer.CosmosExtension
@@ -31,11 +32,13 @@ public IEnumerable<string> GetFieldNames()
3132
{
3233
if (value is JObject element)
3334
{
34-
return new CosmosDictionaryDataItem(element.ToObject<IDictionary<string, object?>>().ToDictionary(k => k.Key, v => v.Value));
35+
return new CosmosDictionaryDataItem(element.ToObject<IDictionary<string, object?>>(JsonSerializer.Create(RawJsonCosmosSerializer.GetDefaultSettings()))
36+
.ToDictionary(k => k.Key, v => v.Value));
3537
}
3638
if (value is JArray array)
3739
{
38-
return array.ToObject<List<object?>>().Select(GetChildObject).ToList();
40+
return array.ToObject<List<object?>>(JsonSerializer.Create(RawJsonCosmosSerializer.GetDefaultSettings()))
41+
.Select(GetChildObject).ToList();
3942
}
4043

4144
return value;

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosExtensionServices.cs

+9-8
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,23 @@ public static CosmosClient CreateClient(CosmosSettingsBase settings, string disp
1515
{
1616
string userAgentString = CreateUserAgentString(displayName, sourceDisplayName);
1717

18+
var cosmosSerializer = new RawJsonCosmosSerializer();
19+
if (settings is CosmosSinkSettings sinkSettings)
20+
{
21+
cosmosSerializer.SerializerSettings.NullValueHandling = sinkSettings.IgnoreNullValues
22+
? Newtonsoft.Json.NullValueHandling.Ignore
23+
: Newtonsoft.Json.NullValueHandling.Include;
24+
}
25+
1826
var clientOptions = new CosmosClientOptions
1927
{
2028
ConnectionMode = settings.ConnectionMode,
2129
ApplicationName = userAgentString,
2230
AllowBulkExecution = true,
2331
EnableContentResponseOnWrite = false,
32+
Serializer = cosmosSerializer,
2433
};
2534

26-
if (settings is CosmosSinkSettings sinkSettings)
27-
{
28-
clientOptions.SerializerOptions = new CosmosSerializationOptions
29-
{
30-
IgnoreNullValues = sinkSettings.IgnoreNullValues
31-
};
32-
}
33-
3435
CosmosClient? cosmosClient;
3536
if (settings.UseRbacAuth)
3637
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Microsoft.Azure.Cosmos;
2+
using Newtonsoft.Json;
3+
using System.Text;
4+
5+
namespace Cosmos.DataTransfer.CosmosExtension;
6+
7+
/// <summary>
8+
/// Serializer for Cosmos allowing access to internal JsonSerializer settings.
9+
/// </summary>
10+
/// <remarks>
11+
/// Defaults to disabling metadata handling to allow passthrough of recognized properties like "$type".
12+
/// </remarks>
13+
public class RawJsonCosmosSerializer : CosmosSerializer
14+
{
15+
private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
16+
17+
public static JsonSerializerSettings GetDefaultSettings() =>
18+
new()
19+
{
20+
DateParseHandling = DateParseHandling.None,
21+
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
22+
ContractResolver = null,
23+
MaxDepth = 64,
24+
};
25+
26+
public JsonSerializerSettings SerializerSettings { get; } = GetDefaultSettings();
27+
28+
public override T FromStream<T>(Stream stream)
29+
{
30+
using (stream)
31+
{
32+
if (typeof(Stream).IsAssignableFrom(typeof(T)))
33+
{
34+
return (T)(object)stream;
35+
}
36+
37+
using var streamReader = new StreamReader(stream);
38+
using var jsonReader = new JsonTextReader(streamReader);
39+
var serializer = JsonSerializer.Create(SerializerSettings);
40+
return serializer.Deserialize<T>(jsonReader);
41+
}
42+
}
43+
44+
public override Stream ToStream<T>(T input)
45+
{
46+
var memoryStream = new MemoryStream();
47+
using (var streamWriter = new StreamWriter(memoryStream, DefaultEncoding, bufferSize: 1024, leaveOpen: true))
48+
{
49+
using (var jsonWriter = new JsonTextWriter(streamWriter))
50+
{
51+
jsonWriter.Formatting = Formatting.None;
52+
var serializer = JsonSerializer.Create(SerializerSettings);
53+
serializer.Serialize(jsonWriter, input);
54+
jsonWriter.Flush();
55+
streamWriter.Flush();
56+
}
57+
}
58+
59+
memoryStream.Position = 0;
60+
return memoryStream;
61+
}
62+
}

Extensions/Json/Cosmos.DataTransfer.JsonExtension.UnitTests/JsonSourceTests.cs

+50
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,56 @@ public async Task ReadAsync_WithFlatObjects_ReadsValuesFromUrl()
154154
}
155155
}
156156

157+
[TestMethod]
158+
public async Task ReadAsync_WithTypeHintFields_IncludesAllInOutput()
159+
{
160+
var json = @"[
161+
{
162+
""id"": 1,
163+
""name"": ""One"",
164+
""$type"": ""Number"",
165+
""data"": {
166+
""$type"": ""Object"",
167+
""name"": ""A""
168+
}
169+
},
170+
{
171+
""id"": 2,
172+
""name"": ""Two"",
173+
""$type"": ""Digit"",
174+
""data"": {
175+
""$type"": ""String"",
176+
""name"": ""B""
177+
}
178+
}
179+
]";
180+
var filePath = Path.Combine(Path.GetTempPath(), "TypeHintFields.json");
157181

182+
await File.WriteAllTextAsync(filePath, json);
183+
184+
var extension = new JsonFileSource();
185+
var config = TestHelpers.CreateConfig(new Dictionary<string, string>
186+
{
187+
{ "FilePath", filePath }
188+
});
189+
190+
int counter = 0;
191+
await foreach (var dataItem in extension.ReadAsync(config, NullLogger.Instance))
192+
{
193+
counter++;
194+
var fields = dataItem.GetFieldNames().ToArray();
195+
CollectionAssert.AreEquivalent(new[] { "id", "name", "$type", "data" }, fields);
196+
Assert.IsNotNull(dataItem.GetValue("id"));
197+
Assert.IsNotNull(dataItem.GetValue("name"));
198+
Assert.IsNotNull(dataItem.GetValue("$type"));
199+
var child = dataItem.GetValue("data") as JsonDictionaryDataItem;
200+
Assert.IsNotNull(child);
201+
CollectionAssert.AreEquivalent(new[] { "$type", "name" }, child.GetFieldNames().ToArray());
202+
Assert.IsNotNull(child.GetValue("$type"));
203+
Assert.IsNotNull(child.GetValue("name"));
204+
}
205+
206+
Assert.AreEqual(2, counter);
207+
}
158208
}
159209
}

0 commit comments

Comments
 (0)