• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {assertDefined, assertTrue} from './assert_utils';
18
19/**
20 * Represents a key in an object, which may be a simple key or an array key.
21 */
22class Key {
23  /**
24   * @param key The key name.
25   * @param index The index of the key in an array, or undefined if it's not an array key.
26   */
27  constructor(public key: string, public index?: number) {}
28
29  /**
30   * Returns true if the key is an array key.
31   */
32  isArrayKey(): boolean {
33    return this.index !== undefined;
34  }
35}
36
37/**
38 * Utility class for working with objects.
39 */
40export class ObjectUtils {
41  private static readonly ARRAY_KEY_REGEX = new RegExp('(.+)\\[(\\d+)\\]');
42
43  /**
44   * Gets the property at the given path in the object.
45   *
46   * @param obj The object to get the property from.
47   * @param path The path to the property, using dot notation for nested objects.
48   * @return The value of the property at the given path.
49   */
50  static getProperty(obj: object, path: string): any {
51    const keys = ObjectUtils.parseKeys(path);
52    keys.forEach((key) => {
53      if (obj === undefined) {
54        return;
55      }
56
57      if (key.isArrayKey()) {
58        if ((obj as any)[key.key] === undefined) {
59          return;
60        }
61        assertTrue(
62          Array.isArray((obj as any)[key.key]),
63          () => 'Expected to be array',
64        );
65        obj = (obj as any)[key.key][assertDefined(key.index)];
66      } else {
67        obj = (obj as any)[key.key];
68      }
69    });
70    return obj;
71  }
72
73  /**
74   * Sets the property at the given path in the object.
75   *
76   * @param obj The object to set the property on.
77   * @param path The path to the property, using dot notation for nested objects.
78   * @param value The value to set the property to.
79   */
80  static setProperty(obj: object, path: string, value: any) {
81    const keys = ObjectUtils.parseKeys(path);
82
83    keys.slice(0, -1).forEach((key) => {
84      if (key.isArrayKey()) {
85        ObjectUtils.initializePropertyArrayIfNeeded(obj, key);
86        obj = (obj as any)[key.key][assertDefined(key.index)];
87      } else {
88        ObjectUtils.initializePropertyIfNeeded(obj, key.key);
89        obj = (obj as any)[key.key];
90      }
91    });
92
93    const lastKey = assertDefined(keys.at(-1));
94    if (lastKey.isArrayKey()) {
95      ObjectUtils.initializePropertyArrayIfNeeded(obj, lastKey);
96      (obj as any)[lastKey.key][assertDefined(lastKey.index)] = value;
97    } else {
98      (obj as any)[lastKey.key] = value;
99    }
100  }
101
102  private static parseKeys(path: string): Key[] {
103    return path.split('.').map((rawKey) => {
104      const match = ObjectUtils.ARRAY_KEY_REGEX.exec(rawKey);
105      if (match) {
106        return new Key(match[1], Number(match[2]));
107      }
108      return new Key(rawKey);
109    });
110  }
111
112  private static initializePropertyIfNeeded(obj: object, key: string) {
113    if ((obj as any)[key] === undefined) {
114      (obj as any)[key] = {};
115    }
116    assertTrue(
117      typeof (obj as any)[key] === 'object',
118      () => 'Expected to be object',
119    );
120  }
121
122  private static initializePropertyArrayIfNeeded(obj: object, key: Key) {
123    if ((obj as any)[key.key] === undefined) {
124      (obj as any)[key.key] = [];
125    }
126    if ((obj as any)[key.key][assertDefined(key.index)] === undefined) {
127      (obj as any)[key.key][assertDefined(key.index)] = {};
128    }
129    assertTrue(
130      Array.isArray((obj as any)[key.key]),
131      () => 'Expected to be array',
132    );
133  }
134}
135