@@ -20,6 +20,29 @@ public JsonObject MapFromDataToStorageModel(TRecord dataModel, int recordIndex,
2020
2121 var jsonObject = JsonSerializer . SerializeToNode ( dataModel , jsonSerializerOptions ) ! . AsObject ( ) ;
2222
23+ // Azure AI Search only supports Edm.DateTimeOffset for date/time types.
24+ // DateTime properties may be serialized by STJ without timezone info (when Kind=Unspecified),
25+ // which Azure AI Search rejects. Convert them to DateTimeOffset (UTC).
26+ // DateOnly properties are serialized as "yyyy-MM-dd", which also isn't accepted.
27+ // Convert them to full DateTimeOffset representation (midnight UTC).
28+ foreach ( var dataProperty in model . DataProperties )
29+ {
30+ var propertyType = Nullable . GetUnderlyingType ( dataProperty . Type ) ?? dataProperty . Type ;
31+
32+ if ( propertyType == typeof ( DateTime ) && jsonObject . TryGetPropertyValue ( dataProperty . StorageName , out var dtNode ) && dtNode is not null )
33+ {
34+ var dateTime = dtNode . Deserialize < DateTime > ( jsonSerializerOptions ) ;
35+ jsonObject [ dataProperty . StorageName ] = JsonValue . Create ( new DateTimeOffset ( DateTime . SpecifyKind ( dateTime , DateTimeKind . Utc ) , TimeSpan . Zero ) ) ;
36+ }
37+ #if NET
38+ else if ( propertyType = = typeof ( DateOnly ) && jsonObject . TryGetPropertyValue ( dataProperty . StorageName , out var dateNode ) && dateNode is not null )
39+ {
40+ var dateOnly = dateNode . Deserialize < DateOnly > ( jsonSerializerOptions ) ;
41+ jsonObject [ dataProperty . StorageName ] = JsonValue . Create ( new DateTimeOffset ( dateOnly . ToDateTime ( TimeOnly . MinValue ) , TimeSpan . Zero ) ) ;
42+ }
43+ #endif
44+ }
45+
2346 // Go over the vector properties; inject any generated embeddings to overwrite the JSON serialized above.
2447 // Also, for Embedding<T> properties we also need to overwrite with a simple array (since Embedding<T> gets serialized as a complex object).
2548 for ( var i = 0 ; i < model . VectorProperties . Count ; i ++ )
@@ -64,6 +87,27 @@ public JsonObject MapFromDataToStorageModel(TRecord dataModel, int recordIndex,
6487
6588 public TRecord MapFromStorageToDataModel ( JsonObject storageModel , bool includeVectors )
6689 {
90+ // Azure AI Search stores DateTime properties as Edm.DateTimeOffset.
91+ // Convert them back to DateTime so STJ can deserialize them into the correct type.
92+ // DateOnly properties also need to be converted back from DateTimeOffset.
93+ foreach ( var dataProperty in model . DataProperties )
94+ {
95+ var propertyType = Nullable . GetUnderlyingType ( dataProperty . Type ) ?? dataProperty . Type ;
96+
97+ if ( propertyType == typeof ( DateTime ) && storageModel . TryGetPropertyValue ( dataProperty . StorageName , out var dtNode ) && dtNode is not null )
98+ {
99+ var dateTimeOffset = dtNode . Deserialize < DateTimeOffset > ( jsonSerializerOptions ) ;
100+ storageModel [ dataProperty . StorageName ] = JsonValue . Create ( dateTimeOffset . UtcDateTime ) ;
101+ }
102+ #if NET
103+ else if ( propertyType = = typeof ( DateOnly ) && storageModel . TryGetPropertyValue ( dataProperty . StorageName , out var dateNode ) && dateNode is not null )
104+ {
105+ var dateTimeOffset = dateNode . Deserialize < DateTimeOffset > ( jsonSerializerOptions ) ;
106+ storageModel [ dataProperty . StorageName ] = JsonValue . Create ( DateOnly . FromDateTime ( dateTimeOffset . DateTime ) ) ;
107+ }
108+ #endif
109+ }
110+
67111 if ( includeVectors )
68112 {
69113 foreach ( var vectorProperty in model . VectorProperties )
0 commit comments