question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Azure Search] Cannot 'select' fields in schemas containing Edm.ComplexType fields using the TypeScript syntax

See original GitHub issue
  • Package Name: @azure/search-documents
  • Package Version: 11.3
  • Operating system: n/a
  • nodejs
    • version:
  • browser
    • name/version:
  • typescript
    • version: 4.8
  • Is the bug related to documentation in

Describe the bug According to https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/search/search-documents/README.md#querying-with-typescript , it should be possible to strongly type queries made using SearchClient. However, the current types only support a subset of fields. It is not possible to query sub-fields of Edm.ComplexType or Collection(Edm.ComplexType).

To Reproduce Here’s a modified version of the documentation that triggers the compilation error:

// An example schema for documents in the index
interface HotelDetails {
  Description: string;
  ParkingIncluded: boolean;
  LastRenovationDate: Date;
  Rating: number;
}
interface Hotel {
  HotelId: string;
  HotelName: string;
  HotelDetails: HotelDetails;  //< Edm.ComplexType
}

const client = new SearchClient<Hotel>(...);

const searchResults = await client.search("wifi -luxury", {
  select: [
    "HotelId",             //< ok
    "HotelName",           //< ok
    "HotelDetails/Rating"  //< compilation error while the reference grammar says it should be ok.
                           //< https://learn.microsoft.com/en-us/azure/search/search-query-odata-select
  ],
});

Expected behavior The client should expect the proper list of fields and produce the correct return type, including when dealing with nested objects and collections.

Additional context This bug is caused by the following definition:

search<Fields extends keyof T>(searchText?: string, options?: SearchOptions<Fields>): Promise<SearchDocumentsResult<Pick<T, Fields>>>;

which uses keyof T to build the list of fields. It should be possible to create a type that deeply traverses T and builds the proper list of keys. In fact, this post on StackOverflow already covers most cases and only needs minor modifications to change the separator character when it comes to generating the list of fields. It would not, however, be enough to deal with the return type of the search function.

EDIT: Here’s a complete version that handles arrays correctly that you can use as the basis to fix this issue:

type Join<K, P> = K extends string | number ?
    P extends string | number ?
    `${K}${"" extends P ? "" : "/"}${P}`
    : never : never;

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]

type Paths<T, D extends number = 10> = D extends never
    ? never
    : T extends object
        ?
            {
                [K in keyof T]-?: K extends string | number
                    ? T[K] extends Array<infer AT>
                        ? `${K}` | Join<K, Paths<AT, Prev[D]>>
                        : `${K}` | Join<K, Paths<T[K], Prev[D]>>
                    : never
            }[keyof T]
            : never

type Foo = {
    foo: number;
    bar: string;
    biz: () => {};
    baz: Foo;
    buzz: { a: string }[];
}

const select: Paths<Foo>[] = ['foo', 'baz/foo', 'buzz/a'];   //< compiles

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
witemple-msftcommented, Oct 6, 2022

I came up with an alternative that I think could address the situation without requiring too much type machinery. They are specialized for the Search $select grammar.

There are two utility types:

  • UnionToIntersection<U>, which converts a union T1 | T2 | ... | TN to an intersection T1 & T2 & ... & TN.
  • ExcludedODataTypes, which is just a union of types that shouldn’t be recursively mapped, currently only Date but might add others as you’ve suggested.

Then the two operative types that appear in the search signature:

  • SearchSelectPaths<T extends object>, which does a post-order traversal of T to form a union of all property paths.
  • SearchDeepPick<T extends object, Paths extends SearchSelectPaths<T>>, which performs the deep pick by recursively creating a union of interfaces each containing a single refined field of T and then converting the whole thing to an intersection.

Heavily-commented TypeScript Playground here. This solution has some nice properties, such as all the paths and fields being IntelliSense auto-completable and without destroying the editor hints with huge serialized types.

@kawazoe It would be awesome if you could try these types with some of the examples that you said were throwing 7056 (type too long) and any other recursion limit errors. I’d love to know how the editor experience looks & whether or not you run into any type errors. I think these definitions should prevent those recursion limit errors by actually convincing the type serializer not the expand the type in the first place. Also, how deeply nested are the types that you are working with?

CC @xirzec @bterlson @joheredi wdyt?

1reaction
kawazoecommented, Oct 19, 2022

@witemple-msft For the array part, it’s just doing a map on each value. Basically, if your type look like this:

type People {
  addresses: [
    { part: string, city: string, country: string, kind: string }
  ]
}

A select query like ['addresses/city', 'addresses/kind'] should produce:

{
  addresses: [
    { city: string, kind: string }
  ]
}

And a query like ['addresses'] should return the entire original array.

In all cases, you cannot limit which element in the array you will be getting. You can only control their structure. Obviously, this is also recursive since you can have an array of complex type in an array of complex type.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Model complex data types - Azure Cognitive Search
In Azure Cognitive Search, complex types are modeled using complex fields. A complex field is a field that contains children (sub-fields) ...
Read more >
Sitecore and Azure Search: support for Edm.ComplexType
I am using Sitecore 9.3, and I would like to add the structured computed field to the Azure Search index.
Read more >
SAP SuccessFactors HXM Suite OData API: Reference Guide ...
Query and Edit Behaviors of Entities with Picklist Fields. ... Rules for Choosing the Right ID in SAP SuccessFactors Learning Web Services.
Read more >
[Solved]-Parse Azure search query filter-C# - appsloveworld
Coding example for the question Parse Azure search query filter-C#. ... the CustomerId sub-field of a top-level Edm.ComplexType field named Customer (Using ......
Read more >
Definición de un ecosistema de herramientas Open Source y ...
While the API is described using JSON it does not impose a JSON input/output to the API itself. All field names in the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found