Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8a75458
feat(dts-generator): add bindList declarations and tests to TypedJSON…
viktorsperling Feb 27, 2026
c78fc1a
feat(dts-generator): add getMessagesByPath declarations and tests to …
viktorsperling Mar 10, 2026
a4faa4d
chore(dts-generator): adds getMessagesByPath declaration
viktorsperling Mar 10, 2026
47435b7
feat(dts-generator): add bindContext declarations and tests to TypedJ…
viktorsperling Mar 24, 2026
abd0d3e
chore(dts-generator): remove unnecessary import
viktorsperling Mar 30, 2026
9607eb0
feat(dts-generator): add bindTree declarations and tests to TypedJSON…
viktorsperling Apr 7, 2026
1fc777e
chore(dts-generator): review comments
viktorsperling Apr 8, 2026
c6a219c
chore(dts-generator): apply changes of other pull requests to reduce …
viktorsperling Apr 9, 2026
969ba94
chore(dts-generator): apply changes of other pull requests to reduce …
viktorsperling Apr 9, 2026
c2b4f75
chore(dts-generator): remove unnecessary imports
viktorsperling Apr 9, 2026
4de24de
chore(dts-generator): remove unnecessary imports
viktorsperling Apr 9, 2026
3323451
chore(dts-generator): apply changes of other pull requests to reduce …
viktorsperling Apr 9, 2026
193ba04
chore(dts-generator): remove unnecessary imports
viktorsperling Apr 9, 2026
04583ea
chore(dts-generator): remove unnecessary diffs in input.ts
viktorsperling Apr 9, 2026
20c4ce6
chore(dts-generator): remove unnecessary tests
viktorsperling Apr 10, 2026
258a35a
feat(dts-generator): merge 'bindList' and 'bindContext' for TypedJSON…
viktorsperling Apr 10, 2026
daa0419
chore(dts-generator): merge 'bindTree'
viktorsperling Apr 10, 2026
26ea415
chore(dts-generator): merge 'getMessagesByPath''
viktorsperling Apr 10, 2026
6c05b1e
chore(dts-generator): merge 'bindProperty'
viktorsperling Apr 10, 2026
1cd3c11
chore(dts-generator): review comments
viktorsperling Apr 10, 2026
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
207 changes: 206 additions & 1 deletion packages/dts-generator/src/resources/typed-json-model.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
declare module "sap/ui/model/json/TypedJSONModel" {
import Message from "sap/ui/core/message/Message";
import ClientContextBinding from "sap/ui/model/ClientContextBinding";
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 JSONPropertyBinding from "sap/ui/model/json/JSONPropertyBinding";
import JSONTreeBinding from "sap/ui/model/json/JSONTreeBinding";
import TypedJSONContext from "sap/ui/model/json/TypedJSONContext";
import Context from "sap/ui/model/Context";

/**
* TypedJSONModel is a subclass of JSONModel that provides type-safe access to the model data. It is only available when using UI5 with TypeScript.
Expand All @@ -18,7 +25,76 @@ declare module "sap/ui/model/json/TypedJSONModel" {
fnCallBack?: Function,
bReload?: boolean,
): TypedJSONContext<Data, Path>;

bindContext<Path extends AbsoluteObjectBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
mParameters?: object,
): ClientContextBinding;
bindContext<
Path extends RelativeObjectBindingPath<Data, Root>,
Root extends AbsoluteObjectBindingPath<Data>,
>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
mParameters?: object,
): ClientContextBinding;

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;

bindProperty<Path extends AbsoluteBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
mParameters?: object,
): JSONPropertyBinding;
bindProperty<
Path extends RelativeBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
mParameters?: object,
): JSONPropertyBinding;

bindTree<Path extends AbsoluteTreeBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding;
bindTree<
Path extends RelativeTreeBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding;

getData(): Data;
getMessagesByPath<Path extends AbsoluteBindingPath<Data>>(
sPath: Path,
bPrefixMatch?: boolean,
): Message[];
getProperty<Path extends AbsoluteBindingPath<Data>>(
sPath: Path,
): PropertyByAbsoluteBindingPath<Data, Path>;
Expand Down Expand Up @@ -82,6 +158,65 @@ 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 absolute binding path for underlying `Array` or `object` types.
*
* @example
* type SalesOrder = { id: string, items: string[], parameters: { weight: number } };
* type PathInObject = PathInJSONModel<SalesOrder>; // "/id" | "/items" | "/parameters"
* let path: PathInObject = "/items"; // ok
* path = "/parameters"; // ok
* path = "/id"; // error
* path = "/items/0"; // error, since an element in the array is a string
*/
export type AbsoluteTreeBindingPath<Type> = {
[Path in AbsoluteBindingPath<Type>]: PropertyByAbsoluteBindingPath<
Type,
Path
> extends Array<unknown>
? Path
: PropertyByAbsoluteBindingPath<Type, Path> extends object
? Path
: never;
}[AbsoluteBindingPath<Type>];

/**
* Valid absolute binding path for underlying object types (excludes arrays and primitives).
*
* @example
* type Order = { customer: { address: { city: string } }, items: string[], total: number };
* type ObjectPaths = AbsoluteObjectBindingPath<Order>; // "/customer" | "/customer/address"
*/
export type AbsoluteObjectBindingPath<Type> = {
[Path in AbsoluteBindingPath<Type>]: PropertyByAbsoluteBindingPath<
Type,
Path
> extends Array<unknown>
? never
: PropertyByAbsoluteBindingPath<Type, Path> extends object
? 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 +233,76 @@ 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>];

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

/**
* Valid relative binding path for underlying object types (excludes arrays and primitives).
* The root of the path is defined by the given root string.
*
* @example
* type SalesOrder = { buyer: { id: string, name: string }, items: string[] };
* type PathRelativeToSalesOrder = RelativeObjectBindingPath<SalesOrder, "/buyer">; // never (no nested objects)
*
* type Order = { customer: { address: { city: string } }, total: number };
* type PathInOrder = RelativeObjectBindingPath<Order, "/">; // "customer" | "customer/address"
*/
export type RelativeObjectBindingPath<
Type,
Root extends AbsoluteBindingPath<Type>,
> = {
[Path in RelativeBindingPath<Type, Root>]: PropertyByRelativeBindingPath<
Type,
Root,
Path
> extends Array<unknown>
? never
: PropertyByRelativeBindingPath<Type, Root, Path> extends object
? Path
: never;
}[RelativeBindingPath<Type, Root>];

/**
* The type of a property in a JSONModel identified by the given path.
* Counterpart to {@link AbsoluteBindingPath}.
Expand Down
2 changes: 1 addition & 1 deletion test-packages/typed-json-model/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"ci": "npm run lint && npm run ui5lint && npm run ts-typecheck && npm run test"
},
"devDependencies": {
"@types/openui5": "1.136.0",
"@openui5/types": "^1.146.0",
"@ui5/cli": "^4.0.30",
"@ui5/linter": "^1.20.2",
"eslint": "^9.37.0",
Expand Down
3 changes: 2 additions & 1 deletion test-packages/typed-json-model/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"baseUrl": "./",
"paths": {},
"composite": true,
"outDir": "./dist"
"outDir": "./dist",
"types": ["@openui5/types"]
},
"include": ["./webapp/**/*"],
"exclude": ["./**/*.mjs", "./webapp/**/test/**"]
Expand Down
116 changes: 116 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,23 @@
import Message from "sap/ui/core/message/Message";
import ClientContextBinding from "sap/ui/model/ClientContextBinding";
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 JSONPropertyBinding from "sap/ui/model/json/JSONPropertyBinding";
import JSONTreeBinding from "sap/ui/model/json/JSONTreeBinding";
import {
AbsoluteBindingPath,
AbsoluteListBindingPath,
PropertyByAbsoluteBindingPath,
PropertyByRelativeBindingPath,
RelativeBindingPath,
RelativeListBindingPath,
AbsoluteObjectBindingPath,
RelativeObjectBindingPath,
AbsoluteTreeBindingPath,
RelativeTreeBindingPath,
} from "./typing";

export class TypedJSONContext<Data extends object, Root extends AbsoluteBindingPath<Data>> extends Context {
Expand Down Expand Up @@ -39,10 +52,113 @@ export class TypedJSONModel<Data extends object> extends JSONModel {
return super.createBindingContext(sPath, oContext, mParameters, fnCallBack, bReload) as TypedJSONContext<Data, Path>;
}

// Overload for absolute paths
bindContext<Path extends AbsoluteObjectBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
mParameters?: object,
): ClientContextBinding;
// Overload for relative paths
bindContext<Path extends RelativeObjectBindingPath<Data, Root>, Root extends AbsoluteObjectBindingPath<Data>>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
mParameters?: object,
): ClientContextBinding;
// Implementation
bindContext<
Path extends AbsoluteObjectBindingPath<Data> | RelativeObjectBindingPath<Data, Root>,
Root extends AbsoluteObjectBindingPath<Data>,
>(sPath: Path, oContext?: TypedJSONContext<Data, Root>, mParameters?: object): ClientContextBinding {
return super.bindContext(sPath, oContext, mParameters);
}

// 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);
}

// Overload for absolute paths
bindProperty<Path extends AbsoluteBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
mParameters?: object,
): JSONPropertyBinding;
// Overload for relative paths
bindProperty<Path extends RelativeBindingPath<Data, Root>, Root extends AbsoluteBindingPath<Data>>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
mParameters?: object,
): JSONPropertyBinding;
bindProperty<
Path extends AbsoluteBindingPath<Data> | RelativeBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(sPath: Path, oContext?: TypedJSONContext<Data, Root>, mParameters?: object): JSONPropertyBinding {
return super.bindProperty(sPath, oContext, mParameters);
}

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

getData(): Data {
return super.getData() as Data;
}

getMessagesByPath<Path extends AbsoluteBindingPath<Data>>(sPath: Path, bPrefixMatch?: boolean): Message[] {
return super.getMessagesByPath(sPath, bPrefixMatch);
}

getProperty<Path extends AbsoluteBindingPath<Data>>(sPath: Path): PropertyByAbsoluteBindingPath<Data, Path>;
getProperty<Path extends RelativeBindingPath<Data, Root>, Root extends AbsoluteBindingPath<Data>>(
sPath: Path,
Expand Down
Loading
Loading