Describe the bug
In these two examples the Azure Search dotnet SDK SearchIndexerSkill deserializer throws exceptions on nullable properties and empty collections.
To get around this we have to put some value of the correct type in those properties of any SearchIndexerSkill.
Expected behavior
Users shouldn't have to provide values for optional properties.
Actual behavior and Reproduction
Example 1 - Nullable properties are required to specified:
If you don't specify what are documented and implemented as nullable properties for a SearchIndexerSkill, the deserializer will throw exceptions.
The EntityRecognitionSkill class definition for reference.
The WebApiSkill class definition for reference.
When you add this EntityRecognitionSkill to a skillset:
var聽entityRecognitionSkill聽=聽new聽EntityRecognitionSkill(inputs,聽outputs)
{
Context聽=聽"/document/finalText/pages/*"
};
Skillset creation will throw this ArgumentNullException:
An unhandled exception of type 'System.ArgumentNullException' occurred in System.Private.CoreLib.dll: 'Value cannot be null.'
at Azure.Search.Documents.Indexes.Models.EntityRecognitionSkillLanguage..ctor(String value) at Azure.Search.Documents.Indexes.Models.EntityRecognitionSkill.DeserializeEntityRecognitionSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkill.DeserializeSearchIndexerSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkillset.DeserializeSearchIndexerSkillset(JsonElement element) at Azure.Search.Documents.SkillsetsRestClient.Create(SearchIndexerSkillset skillset, CancellationToken cancellationToken) at Azure.Search.Documents.Indexes.SearchIndexerClient.CreateSkillset(SearchIndexerSkillset skillset, CancellationToken cancellationToken)
An unhandled exception of type 'System.ArgumentNullException' occurred in System.Private.CoreLib.dll: 'Value cannot be null.'
at Azure.Search.Documents.Indexes.Models.EntityRecognitionSkillLanguage..ctor(String value) at Azure.Search.Documents.Indexes.Models.EntityRecognitionSkill.DeserializeEntityRecognitionSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkill.DeserializeSearchIndexerSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkillset.DeserializeSearchIndexerSkillset(JsonElement element) at Azure.Search.Documents.SkillsetsRestClient.Create(SearchIndexerSkillset skillset, CancellationToken cancellationToken) at Azure.Search.Documents.Indexes.SearchIndexerClient.CreateSkillset(SearchIndexerSkillset skillset, CancellationToken cancellationToken)
Or a WebApiSkill like this to a skillset:
var聽webApiSkill聽=聽new聽WebApiSkill(inputs,聽outputs,聽uri)
{
Description聽=聽"Upload聽image聽data聽to聽the聽annotation聽store",
Context聽=聽"/document/normalized_images/*"
};
Skillset creation will throw this FormatException:
An unhandled exception of type 'System.FormatException' occurred in System.Private.CoreLib.dll: 'The string '' is not a valid TimeSpan value.'
at System.Xml.XmlConvert.ToTimeSpan(String s) at Azure.Search.Documents.Indexes.Models.WebApiSkill.DeserializeWebApiSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkill.DeserializeSearchIndexerSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkillset.DeserializeSearchIndexerSkillset(JsonElement element) at Azure.Search.Documents.SkillsetsRestClient.Create(SearchIndexerSkillset skillset, CancellationToken cancellationToken) at Azure.Search.Documents.Indexes.SearchIndexerClient.CreateSkillset(SearchIndexerSkillset skillset, CancellationToken cancellationToken)
If you assign values to the nullable properties of these skills like below you're able to successfully create the skillset:
var聽entityRecognitionSkill聽=聽new聽EntityRecognitionSkill(inputs,聽outputs)
{
Context聽=聽"/document/finalText/pages/*",
DefaultLanguageCode聽=聽EntityRecognitionSkillLanguage.En聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
};
var聽webApiSkill聽=聽new聽WebApiSkill(inputs,聽outputs,聽uri)
{
Description聽=聽"Upload聽image聽data聽to聽the聽annotation聽store",
Context聽=聽"/document/normalized_images/*",
BatchSize聽=聽1,聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
DegreeOfParallelism聽=聽1,聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
Timeout聽=聽System.TimeSpan.FromSeconds(30)聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
};
Example 2 - An empty dictionary will throw an Invalid Operation Exception
An empty HttpHeaders dictionary in a WebApiSkill throws a InvalidOperationException at deserialization.
The WebApiSkill class definition for reference
When you add this WebApiSkill to a skillset:
var聽webApiSkill聽=聽new聽WebApiSkill(inputs,聽outputs,聽uri)
{
Description聽=聽"Generate聽HOCR聽for聽webpage聽rendering",
Context聽=聽"/document",
BatchSize聽=聽1,聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
DegreeOfParallelism聽=聽1,聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
Timeout聽=聽System.TimeSpan.FromSeconds(30),聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required聽聽聽聽聽聽聽聽聽聽
};
The skillset creation will throw this InvalidOperationException:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Private.CoreLib.dll: 'The requested operation requires an element of type 'Object', but the target element has type 'Null'.'
at System.Text.Json.JsonElement.EnumerateObject() at Azure.Search.Documents.Indexes.Models.WebApiSkill.DeserializeWebApiSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkill.DeserializeSearchIndexerSkill(JsonElement element) at Azure.Search.Documents.Indexes.Models.SearchIndexerSkillset.DeserializeSearchIndexerSkillset(JsonElement element) at Azure.Search.Documents.SkillsetsRestClient.Create(SearchIndexerSkillset skillset, CancellationToken cancellationToken) at Azure.Search.Documents.Indexes.SearchIndexerClient.CreateSkillset(SearchIndexerSkillset skillset, CancellationToken cancellationToken)
To get the skillset to create, you have to put some value in the dictionary even if you don't need it:
var聽webApiSkill聽=聽new聽WebApiSkill(inputs,聽outputs,聽uri)
{
Description聽=聽"Generate聽HOCR聽for聽webpage聽rendering",
Context聽=聽"/document",
BatchSize聽=聽1,聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
DegreeOfParallelism聽=聽1,聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
Timeout聽=聽System.TimeSpan.FromSeconds(30),聽//this聽is聽a聽nullable聽that聽serialization聽enforces聽as聽required
};
//this聽dictionary requires _some_ value
webApiSkill.HttpHeaders["foo"]聽=聽"bar";
Environment:
.NET Core SDK (reflecting any global.json):
Version: 3.1.401
Commit: 39d17847db
Runtime Environment:
OS Name: ubuntu
OS Version: 20.04
OS Platform: Linux
RID: ubuntu.20.04-x64
Thank you for your feedback. Tagging and routing to the team member best able to assist.
Thanks for finding these issues. There appears to be some discrepancy between swagger and our source generator. All required properties should be hoisted to the constructor, which didn't happen here. We can't change existing constructors, but will consider options to make it more obvious what are required properties across different skills.