Elastic.SemanticKernel.Connectors.Elasticsearch/Internal/Helpers/VectorStoreRecordPropertyReader.cs (393 lines of code) (raw):

// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.VectorData; namespace Microsoft.SemanticKernel.Data; #pragma warning disable IDE0007, IDE0028, IDE0032, IDE0059 /// <summary> /// Contains helpers for reading vector store model properties and their attributes. /// </summary> [ExcludeFromCodeCoverage] #pragma warning disable CA1812 // Used in some projects but not all, so need to suppress to avoid warnings in those it's not used in. internal sealed class VectorStoreRecordPropertyReader #pragma warning restore CA1812 { /// <summary>The <see cref="Type"/> of the data model.</summary> private readonly Type _dataModelType; /// <summary>A definition of the current storage model.</summary> private readonly VectorStoreRecordDefinition _vectorStoreRecordDefinition; /// <summary>Options for configuring the behavior of this class.</summary> private readonly VectorStoreRecordPropertyReaderOptions _options; /// <summary>The key properties from the definition.</summary> private readonly List<VectorStoreRecordKeyProperty> _keyProperties; /// <summary>The data properties from the definition.</summary> private readonly List<VectorStoreRecordDataProperty> _dataProperties; /// <summary>The vector properties from the definition.</summary> private readonly List<VectorStoreRecordVectorProperty> _vectorProperties; /// <summary>The <see cref="ConstructorInfo"/> of the parameterless constructor from the data model if one exists.</summary> private readonly Lazy<ConstructorInfo> _parameterlessConstructorInfo; /// <summary>The key <see cref="PropertyInfo"/> objects from the data model.</summary> private List<PropertyInfo>? _keyPropertiesInfo; /// <summary>The data <see cref="PropertyInfo"/> objects from the data model.</summary> private List<PropertyInfo>? _dataPropertiesInfo; /// <summary>The vector <see cref="PropertyInfo"/> objects from the data model.</summary> private List<PropertyInfo>? _vectorPropertiesInfo; /// <summary>A lazy initialized map of data model property names to the names under which they are stored in the data store.</summary> private readonly Lazy<Dictionary<string, string>> _storagePropertyNamesMap; /// <summary>A lazy initialized list of storage names of key properties.</summary> private readonly Lazy<List<string>> _keyPropertyStoragePropertyNames; /// <summary>A lazy initialized list of storage names of data properties.</summary> private readonly Lazy<List<string>> _dataPropertyStoragePropertyNames; /// <summary>A lazy initialized list of storage names of vector properties.</summary> private readonly Lazy<List<string>> _vectorPropertyStoragePropertyNames; /// <summary>A lazy initialized map of data model property names to the names they will have if serialized to JSON.</summary> private readonly Lazy<Dictionary<string, string>> _jsonPropertyNamesMap; /// <summary>A lazy initialized list of json names of key properties.</summary> private readonly Lazy<List<string>> _keyPropertyJsonNames; /// <summary>A lazy initialized list of json names of data properties.</summary> private readonly Lazy<List<string>> _dataPropertyJsonNames; /// <summary>A lazy initialized list of json names of vector properties.</summary> private readonly Lazy<List<string>> _vectorPropertyJsonNames; public VectorStoreRecordPropertyReader( Type dataModelType, VectorStoreRecordDefinition? vectorStoreRecordDefinition, VectorStoreRecordPropertyReaderOptions? options) { this._dataModelType = dataModelType; this._options = options ?? new VectorStoreRecordPropertyReaderOptions(); // If a definition is provided, use it. Otherwise, create one from the type. if (vectorStoreRecordDefinition is not null) { // Here we received a definition, which gives us all of the information we need. // Some mappers though need to set properties on the data model using reflection // so we may still need to find the PropertyInfo objects on the data model later if required. this._vectorStoreRecordDefinition = vectorStoreRecordDefinition; } else { // Here we didn't receive a definition, so we need to derive the information from // the data model. Since we may need the PropertyInfo objects later to read or write // property values on the data model, we save them for later in case we need them. var propertiesInfo = FindPropertiesInfo(dataModelType); this._vectorStoreRecordDefinition = CreateVectorStoreRecordDefinitionFromType(propertiesInfo); this._keyPropertiesInfo = propertiesInfo.KeyProperties; this._dataPropertiesInfo = propertiesInfo.DataProperties; this._vectorPropertiesInfo = propertiesInfo.VectorProperties; } // Verify the definition to make sure it does not have too many or too few of each property type. (this._keyProperties, this._dataProperties, this._vectorProperties) = SplitDefinitionAndVerify( dataModelType.Name, this._vectorStoreRecordDefinition, this._options.SupportsMultipleKeys, this._options.SupportsMultipleVectors, this._options.RequiresAtLeastOneVector); // Setup lazy initializers. this._storagePropertyNamesMap = new Lazy<Dictionary<string, string>>(() => { return BuildPropertyNameToStorageNameMap((this._keyProperties, this._dataProperties, this._vectorProperties)); }); this._parameterlessConstructorInfo = new Lazy<ConstructorInfo>(() => { var constructor = dataModelType.GetConstructor(Type.EmptyTypes); if (constructor == null) { throw new ArgumentException($"Type {dataModelType.FullName} must have a parameterless constructor."); } return constructor; }); this._keyPropertyStoragePropertyNames = new Lazy<List<string>>(() => { var storagePropertyNames = this._storagePropertyNamesMap.Value; return this._keyProperties.Select(x => storagePropertyNames[x.DataModelPropertyName]).ToList(); }); this._dataPropertyStoragePropertyNames = new Lazy<List<string>>(() => { var storagePropertyNames = this._storagePropertyNamesMap.Value; return this._dataProperties.Select(x => storagePropertyNames[x.DataModelPropertyName]).ToList(); }); this._vectorPropertyStoragePropertyNames = new Lazy<List<string>>(() => { var storagePropertyNames = this._storagePropertyNamesMap.Value; return this._vectorProperties.Select(x => storagePropertyNames[x.DataModelPropertyName]).ToList(); }); this._jsonPropertyNamesMap = new Lazy<Dictionary<string, string>>(() => { return BuildPropertyNameToJsonPropertyNameMap( (this._keyProperties, this._dataProperties, this._vectorProperties), dataModelType, this._options.JsonSerializerOptions); }); this._keyPropertyJsonNames = new Lazy<List<string>>(() => { var jsonPropertyNamesMap = this._jsonPropertyNamesMap.Value; return this._keyProperties.Select(x => jsonPropertyNamesMap[x.DataModelPropertyName]).ToList(); }); this._dataPropertyJsonNames = new Lazy<List<string>>(() => { var jsonPropertyNamesMap = this._jsonPropertyNamesMap.Value; return this._dataProperties.Select(x => jsonPropertyNamesMap[x.DataModelPropertyName]).ToList(); }); this._vectorPropertyJsonNames = new Lazy<List<string>>(() => { var jsonPropertyNamesMap = this._jsonPropertyNamesMap.Value; return this._vectorProperties.Select(x => jsonPropertyNamesMap[x.DataModelPropertyName]).ToList(); }); } /// <summary>Gets the record definition of the current storage model.</summary> public VectorStoreRecordDefinition RecordDefinition => this._vectorStoreRecordDefinition; /// <summary>Gets the list of properties from the record definition.</summary> public IReadOnlyList<VectorStoreRecordProperty> Properties => this._vectorStoreRecordDefinition.Properties; /// <summary>Gets the first <see cref="VectorStoreRecordKeyProperty"/> object from the record definition that was provided or that was generated from the data model.</summary> public VectorStoreRecordKeyProperty KeyProperty => this._keyProperties[0]; /// <summary>Gets all <see cref="VectorStoreRecordKeyProperty"/> objects from the record definition that was provided or that was generated from the data model.</summary> public IReadOnlyList<VectorStoreRecordKeyProperty> KeyProperties => this._keyProperties; /// <summary>Gets all <see cref="VectorStoreRecordDataProperty"/> objects from the record definition that was provided or that was generated from the data model.</summary> public IReadOnlyList<VectorStoreRecordDataProperty> DataProperties => this._dataProperties; /// <summary>Gets the first <see cref="VectorStoreRecordVectorProperty"/> objects from the record definition that was provided or that was generated from the data model.</summary> public VectorStoreRecordVectorProperty? VectorProperty => this._vectorProperties.Count > 0 ? this._vectorProperties[0] : null; /// <summary>Gets all <see cref="VectorStoreRecordVectorProperty"/> objects from the record definition that was provided or that was generated from the data model.</summary> public IReadOnlyList<VectorStoreRecordVectorProperty> VectorProperties => this._vectorProperties; /// <summary>Gets the parameterless constructor if one exists, throws otherwise.</summary> public ConstructorInfo ParameterLessConstructorInfo => this._parameterlessConstructorInfo.Value; /// <summary>Gets the first key property info object.</summary> public PropertyInfo KeyPropertyInfo { get { this.LoadPropertyInfoIfNeeded(); return this._keyPropertiesInfo![0]; } } /// <summary>Gets the key property info objects.</summary> public IReadOnlyList<PropertyInfo> KeyPropertiesInfo { get { this.LoadPropertyInfoIfNeeded(); return this._keyPropertiesInfo!; } } /// <summary>Gets the data property info objects.</summary> public IReadOnlyList<PropertyInfo> DataPropertiesInfo { get { this.LoadPropertyInfoIfNeeded(); return this._dataPropertiesInfo!; } } /// <summary>Gets the vector property info objects.</summary> public IReadOnlyList<PropertyInfo> VectorPropertiesInfo { get { this.LoadPropertyInfoIfNeeded(); return this._vectorPropertiesInfo!; } } /// <summary>Gets the name of the first vector property in the definition or null if there are no vectors.</summary> public string? FirstVectorPropertyName => this._vectorProperties.FirstOrDefault()?.DataModelPropertyName; /// <summary>Gets the first vector PropertyInfo object in the data model or null if there are no vectors.</summary> public PropertyInfo? FirstVectorPropertyInfo => this.VectorPropertiesInfo.Count > 0 ? this.VectorPropertiesInfo[0] : null; /// <summary>Gets the property name of the first key property in the definition.</summary> public string KeyPropertyName => this._keyProperties[0].DataModelPropertyName; /// <summary>Gets the storage name of the first key property in the definition.</summary> public string KeyPropertyStoragePropertyName => this._keyPropertyStoragePropertyNames.Value[0]; /// <summary>Gets the storage names of all the properties in the definition.</summary> public IReadOnlyDictionary<string, string> StoragePropertyNamesMap => this._storagePropertyNamesMap.Value; /// <summary>Gets the storage names of the key properties in the definition.</summary> public IReadOnlyList<string> KeyPropertyStoragePropertyNames => this._keyPropertyStoragePropertyNames.Value; /// <summary>Gets the storage names of the data properties in the definition.</summary> public IReadOnlyList<string> DataPropertyStoragePropertyNames => this._dataPropertyStoragePropertyNames.Value; /// <summary>Gets the storage name of the first vector property in the definition or null if there are no vectors.</summary> public string? FirstVectorPropertyStoragePropertyName => this.FirstVectorPropertyName == null ? null : this.StoragePropertyNamesMap[this.FirstVectorPropertyName]; /// <summary>Gets the storage names of the vector properties in the definition.</summary> public IReadOnlyList<string> VectorPropertyStoragePropertyNames => this._vectorPropertyStoragePropertyNames.Value; /// <summary>Gets the json name of the first key property in the definition.</summary> public string KeyPropertyJsonName => this.KeyPropertyJsonNames[0]; /// <summary>Gets the json names of the key properties in the definition.</summary> public IReadOnlyList<string> KeyPropertyJsonNames => this._keyPropertyJsonNames.Value; /// <summary>Gets the json names of the data properties in the definition.</summary> public IReadOnlyList<string> DataPropertyJsonNames => this._dataPropertyJsonNames.Value; /// <summary>Gets the json name of the first vector property in the definition or null if there are no vectors.</summary> public string? FirstVectorPropertyJsonName => this.FirstVectorPropertyName == null ? null : this.JsonPropertyNamesMap[this.FirstVectorPropertyName]; /// <summary>Gets the json names of the vector properties in the definition.</summary> public IReadOnlyList<string> VectorPropertyJsonNames => this._vectorPropertyJsonNames.Value; /// <summary>A map of data model property names to the names they will have if serialized to JSON.</summary> public IReadOnlyDictionary<string, string> JsonPropertyNamesMap => this._jsonPropertyNamesMap.Value; /// <summary>Verify that the data model has a parameterless constructor.</summary> public void VerifyHasParameterlessConstructor() { var constructorInfo = this._parameterlessConstructorInfo.Value; } /// <summary>Verify that the types of the key properties fall within the provided set.</summary> /// <param name="supportedTypes">The list of supported types.</param> public void VerifyKeyProperties(HashSet<Type> supportedTypes) { VectorStoreRecordPropertyVerification.VerifyPropertyTypes(this._keyProperties, supportedTypes, "Key"); } /// <summary>Verify that the types of the data properties fall within the provided set.</summary> /// <param name="supportedTypes">The list of supported types.</param> /// <param name="supportEnumerable">A value indicating whether enumerable types are supported where the element type is one of the supported types.</param> public void VerifyDataProperties(HashSet<Type> supportedTypes, bool supportEnumerable) { VectorStoreRecordPropertyVerification.VerifyPropertyTypes(this._dataProperties, supportedTypes, "Data", supportEnumerable); } /// <summary>Verify that the types of the data properties fall within the provided set.</summary> /// <param name="supportedTypes">The list of supported types.</param> /// <param name="supportedEnumerableElementTypes">A value indicating whether enumerable types are supported where the element type is one of the supported types.</param> public void VerifyDataProperties(HashSet<Type> supportedTypes, HashSet<Type> supportedEnumerableElementTypes) { VectorStoreRecordPropertyVerification.VerifyPropertyTypes(this._dataProperties, supportedTypes, supportedEnumerableElementTypes, "Data"); } /// <summary>Verify that the types of the vector properties fall within the provided set.</summary> /// <param name="supportedTypes">The list of supported types.</param> public void VerifyVectorProperties(HashSet<Type> supportedTypes) { VectorStoreRecordPropertyVerification.VerifyPropertyTypes(this._vectorProperties, supportedTypes, "Vector"); } /// <summary> /// Get the storage property name for the given data model property name. /// </summary> /// <param name="dataModelPropertyName">The data model property name for which to get the storage property name.</param> /// <returns>The storage property name.</returns> public string GetStoragePropertyName(string dataModelPropertyName) { return this._storagePropertyNamesMap.Value[dataModelPropertyName]; } /// <summary> /// Get the name under which a property will be stored if serialized to JSON /// </summary> /// <param name="dataModelPropertyName">The data model property name for which to get the JSON name.</param> /// <returns>The JSON name.</returns> public string GetJsonPropertyName(string dataModelPropertyName) { return this._jsonPropertyNamesMap.Value[dataModelPropertyName]; } /// <summary> /// Check if we have previously loaded the <see cref="PropertyInfo"/> objects from the data model and if not, load them. /// </summary> private void LoadPropertyInfoIfNeeded() { if (this._keyPropertiesInfo != null) { return; } // If we previously built the definition from the data model, the PropertyInfo objects // from the data model would already be saved. If we didn't though, there could be a mismatch // between what is defined in the definition and what is in the data model. Therefore, this // method will throw if any property in the definition is not on the data model. var propertiesInfo = FindPropertiesInfo(this._dataModelType, this._vectorStoreRecordDefinition); this._keyPropertiesInfo = propertiesInfo.KeyProperties; this._dataPropertiesInfo = propertiesInfo.DataProperties; this._vectorPropertiesInfo = propertiesInfo.VectorProperties; } /// <summary> /// Split the given <paramref name="definition"/> into key, data and vector properties and verify that we have the expected numbers of each type. /// </summary> /// <param name="typeName">The name of the type that the definition relates to.</param> /// <param name="definition">The <see cref="VectorStoreRecordDefinition"/> to split.</param> /// <param name="supportsMultipleKeys">A value indicating whether multiple key properties are supported.</param> /// <param name="supportsMultipleVectors">A value indicating whether multiple vectors are supported.</param> /// <param name="requiresAtLeastOneVector">A value indicating whether we need at least one vector.</param> /// <returns>The properties on the <see cref="VectorStoreRecordDefinition"/> split into key, data and vector groupings.</returns> /// <exception cref="ArgumentException">Thrown if there are any validation failures with the provided <paramref name="definition"/>.</exception> private static (List<VectorStoreRecordKeyProperty> KeyProperties, List<VectorStoreRecordDataProperty> DataProperties, List<VectorStoreRecordVectorProperty> VectorProperties) SplitDefinitionAndVerify( string typeName, VectorStoreRecordDefinition definition, bool supportsMultipleKeys, bool supportsMultipleVectors, bool requiresAtLeastOneVector) { var keyProperties = definition.Properties.OfType<VectorStoreRecordKeyProperty>().ToList(); var dataProperties = definition.Properties.OfType<VectorStoreRecordDataProperty>().ToList(); var vectorProperties = definition.Properties.OfType<VectorStoreRecordVectorProperty>().ToList(); if (keyProperties.Count > 1 && !supportsMultipleKeys) { throw new ArgumentException($"Multiple key properties found on type {typeName} or the provided {nameof(VectorStoreRecordDefinition)}."); } if (keyProperties.Count == 0) { throw new ArgumentException($"No key property found on type {typeName} or the provided {nameof(VectorStoreRecordDefinition)}."); } if (requiresAtLeastOneVector && vectorProperties.Count == 0) { throw new ArgumentException($"No vector property found on type {typeName} or the provided {nameof(VectorStoreRecordDefinition)} while at least one is required."); } if (!supportsMultipleVectors && vectorProperties.Count > 1) { throw new ArgumentException($"Multiple vector properties found on type {typeName} or the provided {nameof(VectorStoreRecordDefinition)} while only one is supported."); } return (keyProperties, dataProperties, vectorProperties); } /// <summary> /// Find the properties with <see cref="VectorStoreRecordKeyAttribute"/>, <see cref="VectorStoreRecordDataAttribute"/> and <see cref="VectorStoreRecordVectorAttribute"/> attributes /// and verify that they exist and that we have the expected numbers of each type. /// Return those properties in separate categories. /// </summary> /// <param name="type">The data model to find the properties on.</param> /// <returns>The categorized properties.</returns> private static (List<PropertyInfo> KeyProperties, List<PropertyInfo> DataProperties, List<PropertyInfo> VectorProperties) FindPropertiesInfo(Type type) { List<PropertyInfo> keyProperties = new(); List<PropertyInfo> dataProperties = new(); List<PropertyInfo> vectorProperties = new(); foreach (var property in type.GetProperties()) { // Get Key property. if (property.GetCustomAttribute<VectorStoreRecordKeyAttribute>() is not null) { keyProperties.Add(property); } // Get data properties. if (property.GetCustomAttribute<VectorStoreRecordDataAttribute>() is not null) { dataProperties.Add(property); } // Get Vector properties. if (property.GetCustomAttribute<VectorStoreRecordVectorAttribute>() is not null) { vectorProperties.Add(property); } } return (keyProperties, dataProperties, vectorProperties); } /// <summary> /// Find the properties listed in the <paramref name="vectorStoreRecordDefinition"/> on the <paramref name="type"/> and verify /// that they exist. /// Return those properties in separate categories. /// </summary> /// <param name="type">The data model to find the properties on.</param> /// <param name="vectorStoreRecordDefinition">The property configuration.</param> /// <returns>The categorized properties.</returns> public static (List<PropertyInfo> KeyProperties, List<PropertyInfo> DataProperties, List<PropertyInfo> VectorProperties) FindPropertiesInfo(Type type, VectorStoreRecordDefinition vectorStoreRecordDefinition) { List<PropertyInfo> keyProperties = new(); List<PropertyInfo> dataProperties = new(); List<PropertyInfo> vectorProperties = new(); foreach (VectorStoreRecordProperty property in vectorStoreRecordDefinition.Properties) { // Key. if (property is VectorStoreRecordKeyProperty keyPropertyInfo) { var keyProperty = type.GetProperty(keyPropertyInfo.DataModelPropertyName); if (keyProperty == null) { throw new ArgumentException($"Key property '{keyPropertyInfo.DataModelPropertyName}' not found on type {type.FullName}."); } keyProperties.Add(keyProperty); } // Data. else if (property is VectorStoreRecordDataProperty dataPropertyInfo) { var dataProperty = type.GetProperty(dataPropertyInfo.DataModelPropertyName); if (dataProperty == null) { throw new ArgumentException($"Data property '{dataPropertyInfo.DataModelPropertyName}' not found on type {type.FullName}."); } dataProperties.Add(dataProperty); } // Vector. else if (property is VectorStoreRecordVectorProperty vectorPropertyInfo) { var vectorProperty = type.GetProperty(vectorPropertyInfo.DataModelPropertyName); if (vectorProperty == null) { throw new ArgumentException($"Vector property '{vectorPropertyInfo.DataModelPropertyName}' not found on type {type.FullName}."); } vectorProperties.Add(vectorProperty); } else { throw new ArgumentException($"Unknown property type '{property.GetType().FullName}' in vector store record definition."); } } return (keyProperties, dataProperties, vectorProperties); } /// <summary> /// Create a <see cref="VectorStoreRecordDefinition"/> by reading the attributes on the provided <see cref="PropertyInfo"/> objects. /// </summary> /// <param name="propertiesInfo"><see cref="PropertyInfo"/> objects to build a <see cref="VectorStoreRecordDefinition"/> from.</param> /// <returns>The <see cref="VectorStoreRecordDefinition"/> based on the given <see cref="PropertyInfo"/> objects.</returns> private static VectorStoreRecordDefinition CreateVectorStoreRecordDefinitionFromType((List<PropertyInfo> KeyProperties, List<PropertyInfo> DataProperties, List<PropertyInfo> VectorProperties) propertiesInfo) { var definitionProperties = new List<VectorStoreRecordProperty>(); // Key properties. foreach (var keyProperty in propertiesInfo.KeyProperties) { var keyAttribute = keyProperty.GetCustomAttribute<VectorStoreRecordKeyAttribute>(); if (keyAttribute is not null) { definitionProperties.Add(new VectorStoreRecordKeyProperty(keyProperty.Name, keyProperty.PropertyType) { StoragePropertyName = keyAttribute.StoragePropertyName }); } } // Data properties. foreach (var dataProperty in propertiesInfo.DataProperties) { var dataAttribute = dataProperty.GetCustomAttribute<VectorStoreRecordDataAttribute>(); if (dataAttribute is not null) { definitionProperties.Add(new VectorStoreRecordDataProperty(dataProperty.Name, dataProperty.PropertyType) { IsFilterable = dataAttribute.IsFilterable, IsFullTextSearchable = dataAttribute.IsFullTextSearchable, StoragePropertyName = dataAttribute.StoragePropertyName }); } } // Vector properties. foreach (var vectorProperty in propertiesInfo.VectorProperties) { var vectorAttribute = vectorProperty.GetCustomAttribute<VectorStoreRecordVectorAttribute>(); if (vectorAttribute is not null) { definitionProperties.Add(new VectorStoreRecordVectorProperty(vectorProperty.Name, vectorProperty.PropertyType) { Dimensions = vectorAttribute.Dimensions, IndexKind = vectorAttribute.IndexKind, DistanceFunction = vectorAttribute.DistanceFunction, StoragePropertyName = vectorAttribute.StoragePropertyName }); } } return new VectorStoreRecordDefinition { Properties = definitionProperties }; } /// <summary> /// Build a map of property names to the names under which they should be saved in storage, for the given properties. /// </summary> /// <param name="properties">The properties to build the map for.</param> /// <returns>The map from property names to the names under which they should be saved in storage.</returns> private static Dictionary<string, string> BuildPropertyNameToStorageNameMap((List<VectorStoreRecordKeyProperty> keyProperties, List<VectorStoreRecordDataProperty> dataProperties, List<VectorStoreRecordVectorProperty> vectorProperties) properties) { var storagePropertyNameMap = new Dictionary<string, string>(); foreach (var keyProperty in properties.keyProperties) { storagePropertyNameMap.Add(keyProperty.DataModelPropertyName, keyProperty.StoragePropertyName ?? keyProperty.DataModelPropertyName); } foreach (var dataProperty in properties.dataProperties) { storagePropertyNameMap.Add(dataProperty.DataModelPropertyName, dataProperty.StoragePropertyName ?? dataProperty.DataModelPropertyName); } foreach (var vectorProperty in properties.vectorProperties) { storagePropertyNameMap.Add(vectorProperty.DataModelPropertyName, vectorProperty.StoragePropertyName ?? vectorProperty.DataModelPropertyName); } return storagePropertyNameMap; } /// <summary> /// Build a map of property names to the names that they would have if serialized to JSON. /// </summary> /// <param name="properties">The properties to build the map for.</param> /// <param name="dataModel">The data model type that the property belongs to.</param> /// <param name="options">The options used for JSON serialization.</param> /// <returns>The map from property names to the names that they would have if serialized to JSON.</returns> private static Dictionary<string, string> BuildPropertyNameToJsonPropertyNameMap( (List<VectorStoreRecordKeyProperty> keyProperties, List<VectorStoreRecordDataProperty> dataProperties, List<VectorStoreRecordVectorProperty> vectorProperties) properties, Type dataModel, JsonSerializerOptions options) { var jsonPropertyNameMap = new Dictionary<string, string>(); foreach (var keyProperty in properties.keyProperties) { jsonPropertyNameMap.Add(keyProperty.DataModelPropertyName, GetJsonPropertyName(keyProperty, dataModel, options)); } foreach (var dataProperty in properties.dataProperties) { jsonPropertyNameMap.Add(dataProperty.DataModelPropertyName, GetJsonPropertyName(dataProperty, dataModel, options)); } foreach (var vectorProperty in properties.vectorProperties) { jsonPropertyNameMap.Add(vectorProperty.DataModelPropertyName, GetJsonPropertyName(vectorProperty, dataModel, options)); } return jsonPropertyNameMap; } /// <summary> /// Get the JSON property name of a property by using the <see cref="JsonPropertyNameAttribute"/> if available, otherwise /// using the <see cref="JsonNamingPolicy"/> if available, otherwise falling back to the property name. /// The provided <paramref name="dataModel"/> may not actually contain the property, e.g. when the user has a data model that /// doesn't resemble the stored data and where they are using a custom mapper. /// </summary> /// <param name="property">The property to retrieve a JSON name for.</param> /// <param name="dataModel">The data model type that the property belongs to.</param> /// <param name="options">The options used for JSON serialization.</param> /// <returns>The JSON property name.</returns> private static string GetJsonPropertyName(VectorStoreRecordProperty property, Type dataModel, JsonSerializerOptions options) { var propertyInfo = dataModel.GetProperty(property.DataModelPropertyName); if (propertyInfo != null) { var jsonPropertyNameAttribute = propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>(); if (jsonPropertyNameAttribute is not null) { return jsonPropertyNameAttribute.Name; } } if (options.PropertyNamingPolicy is not null) { return options.PropertyNamingPolicy.ConvertName(property.DataModelPropertyName); } return property.DataModelPropertyName; } } #pragma warning restore IDE0007, IDE0028, IDE0032, IDE0059