Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/dts-generator/src/resources/typed-json-model.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
declare module "sap/ui/model/json/TypedJSONModel" {
import Filter from "sap/ui/model/Filter";
import Sorter from "sap/ui/model/Sorter";
import JSONModel from "sap/ui/model/json/JSONModel";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";
import TypedJSONContext from "sap/ui/model/json/TypedJSONContext";
import Context from "sap/ui/model/Context";

Expand Down Expand Up @@ -30,6 +33,24 @@ declare module "sap/ui/model/json/TypedJSONModel" {
oContext: TypedJSONContext<Data, Root>,
): PropertyByRelativeBindingPath<Data, Root, Path>;

bindList<Path extends AbsoluteListBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
aSorters?: Sorter | Sorter[],
aFilters?: Filter | Filter[],
mParameters?: object,
): JSONListBinding;
bindList<
Path extends RelativeListBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(
sPath: Path,
oContext?: TypedJSONContext<Data, Root>,
aSorters?: Sorter | Sorter[],
aFilters?: Filter | Filter[],
mParameters?: object,
): JSONListBinding;

setData(oData: Data, bMerge?: boolean): void;

// setProperty with AbsoluteBindingPath (context === undefined),
Expand Down Expand Up @@ -82,6 +103,25 @@ declare module "sap/ui/model/json/TypedJSONModel" {
: // if T is not of type object:
never;

/**
* Valid absolute binding path for underlying `Array` types.
*
* @example
* type SalesOrder = { id: string, items: string[] };
* type PathInObject = PathInJSONModel<SalesOrder>; // "/id" | "/items"
* let path: PathInObject = "/items"; // ok
* path = "/id"; // error
* path = "/items/0"; // error, since an element in the array is a string
*/
export type AbsoluteListBindingPath<Type> = {
[Path in AbsoluteBindingPath<Type>]: PropertyByAbsoluteBindingPath<
Type,
Path
> extends Array<unknown>
? Path
: never;
}[AbsoluteBindingPath<Type>];

/**
* Valid relative binding path in a JSONModel.
* The root of the path is defined by the given root string.
Expand All @@ -98,6 +138,27 @@ declare module "sap/ui/model/json/TypedJSONModel" {
? Rest
: never;

/**
* Valid relative binding path for underlying `Array` types.
* The root of the path is defined by the given root string.
*
* @example
* type SalesOrder = { buyer: { id: string, items: string[] } };
* type PathRelativeToSalesOrder = RelativeListBindingPath<SalesOrderWrapper, "/buyer">; // "id" | "items"
*/
export type RelativeListBindingPath<
Type,
Root extends AbsoluteBindingPath<Type>,
> = {
[Path in RelativeBindingPath<Type, Root>]: PropertyByRelativeBindingPath<
Type,
Root,
Path
> extends Array<unknown>
? Path
: never;
}[RelativeBindingPath<Type, Root>];

/**
* The type of a property in a JSONModel identified by the given path.
* Counterpart to {@link AbsoluteBindingPath}.
Expand Down
35 changes: 35 additions & 0 deletions test-packages/typed-json-model/webapp/model/model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import Context from "sap/ui/model/Context";
import Filter from "sap/ui/model/Filter";
import Sorter from "sap/ui/model/Sorter";
import JSONModel from "sap/ui/model/json/JSONModel";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";
import {
AbsoluteBindingPath,
AbsoluteListBindingPath,
PropertyByAbsoluteBindingPath,
PropertyByRelativeBindingPath,
RelativeBindingPath,
RelativeListBindingPath,
} from "./typing";

export class TypedJSONContext<Data extends object, Root extends AbsoluteBindingPath<Data>> extends Context {
Expand Down Expand Up @@ -57,6 +62,36 @@ export class TypedJSONModel<Data extends object> extends JSONModel {
| PropertyByRelativeBindingPath<Data, Root, Path>;
}

// Overload for absolute paths
bindList<Path extends AbsoluteListBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
aSorters?: Sorter | Sorter[],
aFilters?: Filter | Filter[],
mParameters?: object,
): JSONListBinding;
// Overload for relative paths
bindList<Path extends RelativeListBindingPath<Data, Root>, Root extends AbsoluteBindingPath<Data>>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
aSorters?: Sorter | Sorter[],
aFilters?: Filter | Filter[],
mParameters?: object,
): JSONListBinding;
// Implementation
bindList<
Path extends AbsoluteListBindingPath<Data> | RelativeListBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(
sPath: Path,
oContext?: TypedJSONContext<Data, Root>,
aSorters?: Sorter | Sorter[],
aFilters?: Filter | Filter[],
mParameters?: object,
): JSONListBinding {
return super.bindList(sPath, oContext, aSorters, aFilters, mParameters);
}

setData(oData: Data, bMerge?: boolean): void {
super.setData(oData, bMerge);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { JSONSafe, objectLikeByInference, Placeholder } from "../input";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";

import { TypedJSONModel } from "../../model";

Expand Down Expand Up @@ -76,3 +77,22 @@ import { TypedJSONModel } from "../../model";

/** @expect ts2740 */ const dataB: Array<any> = model.getData();
/** @expect ts2345 */ model.setData(dataB);

/***********************************************************************************************************************
* Check model.bindList
**********************************************************************************************************************/

/** @expect ok */ let listBinding: JSONListBinding = model.bindList("/anArray");
/** @expect ok */ listBinding = model.bindList("/anArrayOfArrays/0");
/** @expect ok */ listBinding = model.bindList("/anObjectWithArray/anArray");

// incorrect binding paths
/** @expect ts2345 */ listBinding = model.bindList("/aJsonSafeArray/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfArrays/0/0");
/** @expect ts2345 */ listBinding = model.bindList("/anObjectWithArray/anArray/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfPlaceholders/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfObjects/0");

// bindList always returns a JSONListBinding and cannot be assigned to other types
/** @expect ts2739 */ aPlaceholder = model.bindList("/anArray");
/** @expect ts2322 */ aJsonSafe = model.bindList("/anArray");
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { IObjectLike, JSONSafe, objectLikeByInterface, Placeholder, TObjectLike } from "../input";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";

import { TypedJSONModel } from "../../model";

Expand Down Expand Up @@ -81,3 +82,22 @@ import { TypedJSONModel } from "../../model";

/** @expect ts2740 */ const dataC: Array<any> = model.getData();
/** @expect ts2345 */ model.setData(dataC);

/***********************************************************************************************************************
* Check model.bindList
**********************************************************************************************************************/

/** @expect ok */ let listBinding: JSONListBinding = model.bindList("/anArray");
/** @expect ok */ listBinding = model.bindList("/anArrayOfArrays/0");
/** @expect ok */ listBinding = model.bindList("/anObjectWithArray/anArray");

// incorrect binding paths
/** @expect ts2345 */ listBinding = model.bindList("/aJsonSafeArray/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfArrays/0/0");
/** @expect ts2345 */ listBinding = model.bindList("/anObjectWithArray/anArray/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfPlaceholders/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfObjects/0");

// bindList always returns a JSONListBinding and cannot be assigned to other types
/** @expect ts2739 */ aPlaceholder = model.bindList("/anArray");
/** @expect ts2322 */ aJsonSafe = model.bindList("/anArray");
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { IObjectLike, JSONSafe, objectLikeByTypeAlias, Placeholder, TObjectLike } from "../input";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";

import { TypedJSONModel } from "../../model";

Expand Down Expand Up @@ -81,3 +82,22 @@ import { TypedJSONModel } from "../../model";

/** @expect ts2740 */ const dataC: Array<any> = model.getData();
/** @expect ts2345 */ model.setData(dataC);

/***********************************************************************************************************************
* Check model.bindList
**********************************************************************************************************************/

/** @expect ok */ let listBinding: JSONListBinding = model.bindList("/anArray");
/** @expect ok */ listBinding = model.bindList("/anArrayOfArrays/0");
/** @expect ok */ listBinding = model.bindList("/anObjectWithArray/anArray");

// incorrect binding paths
/** @expect ts2345 */ listBinding = model.bindList("/aJsonSafeArray/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfArrays/0/0");
/** @expect ts2345 */ listBinding = model.bindList("/anObjectWithArray/anArray/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfPlaceholders/0");
/** @expect ts2345 */ listBinding = model.bindList("/anArrayOfObjects/0");

// bindList always returns a JSONListBinding and cannot be assigned to other types
/** @expect ts2739 */ aPlaceholder = model.bindList("/anArray");
/** @expect ts2322 */ aJsonSafe = model.bindList("/anArray");
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { JSONSafe, objectLikeByInference, Placeholder } from "../input";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";

import { TypedJSONModel } from "../../model";

Expand Down Expand Up @@ -67,3 +68,22 @@ import { TypedJSONModel } from "../../model";
/** @expect ts2322 */ aJsonSafe = model.getProperty("anArrayOfPlaceholders/0", context);
/** @expect ts2322 */ anElementInATuple = model.getProperty("aTuple", context);
/** @expect ts2322 */ anObject = model.getProperty("aTuple/0", context);

/***********************************************************************************************************************
* Check model.bindList
**********************************************************************************************************************/

/** @expect ok */ let listBinding: JSONListBinding = model.bindList("anArray", context);
/** @expect ok */ listBinding = model.bindList("anArrayOfArrays/0", context);
/** @expect ok */ listBinding = model.bindList("anObjectWithArray/anArray", context);

// incorrect binding paths
/** @expect ts2769 */ listBinding = model.bindList("aJsonSafeArray/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfArrays/0/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anObjectWithArray/anArray/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfPlaceholders/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfObjects/0", context);

// bindList always returns a JSONListBinding and cannot be assigned to other types
/** @expect ts2739 */ aPlaceholder = model.bindList("anArray", context);
/** @expect ts2322 */ aJsonSafe = model.bindList("anArray", context);
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { JSONSafe, objectLikeByInterface, Placeholder } from "../input";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";

import { TypedJSONModel } from "../../model";

Expand Down Expand Up @@ -67,3 +68,22 @@ import { TypedJSONModel } from "../../model";
/** @expect ts2322 */ aJsonSafe = model.getProperty("anArrayOfPlaceholders/0", context);
/** @expect ts2322 */ anElementInATuple = model.getProperty("aTuple", context);
/** @expect ts2322 */ anObject = model.getProperty("aTuple/0", context);

/***********************************************************************************************************************
* Check model.bindList
**********************************************************************************************************************/

/** @expect ok */ let listBinding: JSONListBinding = model.bindList("anArray", context);
/** @expect ok */ listBinding = model.bindList("anArrayOfArrays/0", context);
/** @expect ok */ listBinding = model.bindList("anObjectWithArray/anArray", context);

// incorrect binding paths
/** @expect ts2769 */ listBinding = model.bindList("aJsonSafeArray/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfArrays/0/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anObjectWithArray/anArray/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfPlaceholders/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfObjects/0", context);

// bindList always returns a JSONListBinding and cannot be assigned to other types
/** @expect ts2739 */ aPlaceholder = model.bindList("anArray", context);
/** @expect ts2322 */ aJsonSafe = model.bindList("anArray", context);
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { JSONSafe, objectLikeByTypeAlias, Placeholder } from "../input";
import JSONListBinding from "sap/ui/model/json/JSONListBinding";

import { TypedJSONModel } from "../../model";

Expand Down Expand Up @@ -69,3 +70,22 @@ model.getProperty("/root/aPlaceholder", context);
/** @expect ts2322 */ aJsonSafe = model.getProperty("anArrayOfPlaceholders/0", context);
/** @expect ts2322 */ anElementInATuple = model.getProperty("aTuple", context);
/** @expect ts2322 */ anObject = model.getProperty("aTuple/0", context);

/***********************************************************************************************************************
* Check model.bindList
**********************************************************************************************************************/

/** @expect ok */ let listBinding: JSONListBinding = model.bindList("anArray", context);
/** @expect ok */ listBinding = model.bindList("anArrayOfArrays/0", context);
/** @expect ok */ listBinding = model.bindList("anObjectWithArray/anArray", context);

// incorrect binding paths
/** @expect ts2769 */ listBinding = model.bindList("aJsonSafeArray/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfArrays/0/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anObjectWithArray/anArray/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfPlaceholders/0", context);
/** @expect ts2769 */ listBinding = model.bindList("anArrayOfObjects/0", context);

// bindList always returns a JSONListBinding and cannot be assigned to other types
/** @expect ts2739 */ aPlaceholder = model.bindList("anArray", context);
/** @expect ts2322 */ aJsonSafe = model.bindList("anArray", context);
19 changes: 19 additions & 0 deletions test-packages/typed-json-model/webapp/model/test/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ export interface IPrimitives {
export type TObjectLike = {
anObject: object;
anArray: Array<unknown>;
anArrayOfArrays: Array<Array<unknown>>;
aJsonSafeArray: Array<JSONSafe>;
anObjectWithArray: { anArray: Array<unknown> };
anArrayOfPlaceholders: Array<Placeholder>;
aPlaceholder: Placeholder;
aTuple: [string, number];
Expand All @@ -71,7 +73,9 @@ export type TObjectLike = {
export interface IObjectLike {
anObject: object;
anArray: Array<unknown>;
anArrayOfArrays: Array<Array<unknown>>;
aJsonSafeArray: Array<JSONSafe>;
anObjectWithArray: { anArray: Array<unknown> };
anArrayOfPlaceholders: Array<Placeholder>;
aPlaceholder: Placeholder;
aTuple: [string, number];
Expand Down Expand Up @@ -123,8 +127,13 @@ export const primitivesByInference = {
export const objectLikeByTypeAlias: TObjectLike = {
anObject: {},
anArray: [],
anArrayOfArrays: [
["string", 1],
[true, false],
],
aJsonSafeArray: ["string", 1, true],
anArrayOfPlaceholders: [new Placeholder()],
anObjectWithArray: { anArray: ["string"] },
aPlaceholder: new Placeholder(),
aTuple: ["string", 1],
};
Expand All @@ -136,8 +145,13 @@ export const objectLikeByTypeAlias: TObjectLike = {
export const objectLikeByInterface: IObjectLike = {
anObject: {},
anArray: [],
anArrayOfArrays: [
["string", 1],
[true, false],
],
aJsonSafeArray: ["string", 1, true],
anArrayOfPlaceholders: [new Placeholder()],
anObjectWithArray: { anArray: ["string"] },
aPlaceholder: new Placeholder(),
aTuple: ["string", 1],
};
Expand All @@ -149,8 +163,13 @@ export const objectLikeByInterface: IObjectLike = {
export const objectLikeByInference = {
anObject: {},
anArray: [],
anArrayOfArrays: [
["string", 1],
[true, false],
],
aJsonSafeArray: ["string", 1, true],
anArrayOfObjects: [{ aNumber: 1 }],
anObjectWithArray: { anArray: ["string"] },
anArrayOfPlaceholders: [new Placeholder()],
aPlaceholder: new Placeholder(),
aTuple: ["string", 1],
Expand Down
Loading
Loading