Typescriptインターフェイスはプロパティの共起制約を表現できますか

Aug 17 2020

プロパティをアサートするためのモノリシックTypescriptインターフェイスまたはタイプ定義内に、プロパティが一緒に表示されるか、まったく表示されない標準パターンがありますか?

たとえば、アイテムが次のようになっている場合、アイテムは有効である可能性があります...

{
  id:"ljklkj",
  spellcheck:true,
  spellcheckModel:"byzantine",
}

...またはこれ...

{
  id:"ljklkj",
}

ただし、いずれかのスペルチェックプロパティが単独で発生した場合は無効になります。

{
  id:"ljklkj",
  spellcheckModel:"byzantine",
}
{
  id:"ljklkj",
  spellcheck:true,
}

モノリシック

もちろん、上記の単純なケースは、データとSpellcheckDataタイプまたはインターフェイスを作成することで解決できます。ただし、私のアプリケーションの場合、同時発生するプロパティの「クラスター」が複数存在します。共起のすべての組み合わせに対して新しいタイプを定義すると、ケースを表現するためにタイプが爆発的に増加します。

このため、私はこのソリューションを「モノリシック」インターフェースと呼んでいます。もちろん、それを定義するために何らかの形の構成を使用する必要があるかもしれません。

私が試したこと

Typescript言語リファレンス内でこのような例を見つけようとしましたが、その機能が何と呼ばれるか(または実際に表現できる機能であるかどうか)がわからないため、苦労しています。プロパティは個別にオプションにすることができますが、共起を表現する方法がわかりません。

関連技術

XMLデータ検証の同等の機能についてここで説明します... https://www.w3.org/wiki/Co-occurrence_constraints

JSONの場合、SchematronやJson ContentRulesなどのスキーマ言語が共制約を表現できることを理解しています。

実施例

Solr検索エンジンのHTTPパラメーターセットに適用される共制約ケースのtypescript構文を想像すると、次のようになります。これは、SpellまたはGroupパラメーターを完全に満たすか、まったく満たさないかを選択できることを示しています。各タイプがオプションであるユニオン(?で示されます)..。

type SolrPassthru =
  SolrCoreParams & (
    SolrSpellParams? |
    SolrGroupParams?  
  )

これは、正しいTypescriptであると私が信じている以下の例とは対照的ですが、パラメーターの各グループからすべてのパラメーターが必要です。

type SolrCoreParams = {
  defType: SolrDefType,
  boost: SolrBoostType,
}

type SolrSpellParams = {
  spellcheck: "true" | "false",
  "spellcheck.collate": "true" | "false",
  "spellcheck.maxCollationTries": 1,
}

type SolrGroupParams = {
  group: "true" | "false",
  "group.limit": '4'
  "group.sort": 'group_level asc,score desc,published desc,text_sort asc'
  "group.main": 'true'
  "group.field": 'group_uri'
}

type SolrPassthru =
  SolrCoreParams & 
  SolrSpellParams &
  SolrGroupParams

回答

3 Lesiak Aug 17 2020 at 15:45

以下をお試しください。正しい場所にエラーが表示されているようです。

type None<T> = {[K in keyof T]?: never}
type EitherOrBoth<T1, T2> = T1 & None<T2> | T2 & None<T1> | T1 & T2

interface Data {
  id: string;
}

interface SpellCheckData {
  spellcheck: boolean,
  spellcheckModel: string,
}

// Two interfaces
var z1: EitherOrBoth<Data, SpellCheckData> = { id: "" };
var z2: EitherOrBoth<Data, SpellCheckData> = { spellcheck: true,  spellcheckModel: 'm'};
var z3ERROR: EitherOrBoth<Data, SpellCheckData> = { spellcheck: true};
var z4: EitherOrBoth<Data, SpellCheckData> = { id: "", spellcheck: true,  spellcheckModel: 'm'};

interface MoreData {
  p1: string,
  p2: string,
  p3: string,
}

type Monolith = EitherOrBoth<Data, EitherOrBoth<SpellCheckData, MoreData>>

var x1: Monolith  = { id: "" };
var x2: Monolith  = { spellcheck: true,  spellcheckModel: 'm'};
var x3ERROR: Monolith  = { spellcheck: true};                       
var x4: Monolith  = { id: "", spellcheck: true,  spellcheckModel: 'm'};
var x5ERROR: Monolith  = { p1: ""};                                  
var x6ERROR: Monolith  = { p1: "", p2: ""};
var x7: Monolith  = { p1: "", p2: "", p3: ""};
var x8: Monolith  = { id: "", p1: "", p2: "", p3: ""};
var x9ERROR: Monolith  = { id: "", spellcheck: true, p1: "", p2: "", p3: ""};
var x10: Monolith  = { id: "", spellcheck: true, spellcheckModel: 'm', p1: "", p2: "", p3: ""};

遊び場リンク

更新

タイプをタプルとして渡す場合は、次のユーティリティを使用できます。

type CombinationOf<T> = T extends [infer U1, infer U2] ? EitherOrBoth<U1, U2> :
                        T extends [infer U1, infer U2, infer U3] ? EitherOrBoth<U1, EitherOrBoth<U2, U3>> :
                        T extends [infer U1, infer U2, infer U3, infer U4] ? EitherOrBoth<U1, EitherOrBoth<U2, EitherOrBoth<U3, U4>>> :
                        never;

type Monolith = CombinationOf<[Data, SpellCheckData, MoreData]>

一部のプロパティが必要な場合:

type Monolith = Data & CombinationOf<[Data, SpellCheckData, MoreData]>