xref: /aosp_15_r20/external/perfetto/ui/src/base/object_utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {assertExists} from './logging';
16import {exists} from './utils';
17
18export type PathKey = string | number;
19export type Path = PathKey[];
20
21/**
22 * Gets the |value| at a |path| of |object|. If a portion of the path doesn't
23 * exist, |undefined| is returned.
24 *
25 * Example:
26 * const obj = {
27 *   a: [
28 *     {b: 'c'},
29 *     {d: 'e', f: 123},
30 *   ],
31 * };
32 * getPath(obj, ['a']) -> [{b: 'c'}, {d: 'e', f: 123}]
33 * getPath(obj, ['a', 1]) -> {d: 'e', f: 123}
34 * getPath(obj, ['a', 1, 'd']) -> 'e'
35 * getPath(obj, ['g']) -> undefined
36 * getPath(obj, ['g', 'h']) -> undefined
37 *
38 * Note: This is an appropriate use of `any`, as we are knowingly getting fast
39 * and loose with the type system in this function: it's basically JavaScript.
40 * Attempting to pretend it's anything else would result in superfluous type
41 * assertions which would serve no benefit.
42 */
43// eslint-disable-next-line @typescript-eslint/no-explicit-any
44export function getPath<T>(obj: any, path: Path): T | undefined {
45  let x = obj;
46  for (const node of path) {
47    if (x === undefined) return undefined;
48    x = x[node];
49  }
50  return x;
51}
52
53/**
54 * Sets the |value| at |path| of |object|. If the final node of the path doesn't
55 * exist, the value will be created. Otherwise, TypeError is thrown.
56 *
57 * Example:
58 * const obj = {
59 *   a: [
60 *     {b: 'c'},
61 *     {d: 'e', f: 123},
62 *   ],
63 * };
64 * setPath(obj, ['a'], 'foo') -> {a: 'foo'}
65 * setPath(obj, ['a', 1], 'foo') -> {a: [{b: 'c'}, 'foo']}
66 * setPath(obj, ['g'], 'foo') -> {a: [...], g: 'foo'}
67 * setPath(obj, ['g', 'h'], 'foo') -> TypeError!
68 */
69// eslint-disable-next-line @typescript-eslint/no-explicit-any
70export function setPath<T>(obj: any, path: Path, value: T): void {
71  const pathClone = [...path];
72  let o = obj;
73  while (pathClone.length > 1) {
74    const p = assertExists(pathClone.shift());
75    o = o[p];
76  }
77
78  const p = pathClone.shift();
79  if (!exists(p)) {
80    throw TypeError('Path array is empty');
81  }
82  o[p] = value;
83}
84
85export function shallowEquals(a: unknown, b: unknown) {
86  if (a === b) {
87    return true;
88  }
89  if (a === undefined || b === undefined) {
90    return false;
91  }
92  if (a === null || b === null) {
93    return false;
94  }
95  const objA = a as {[_: string]: {}};
96  const objB = b as {[_: string]: {}};
97  for (const key of Object.keys(objA)) {
98    if (objA[key] !== objB[key]) {
99      return false;
100    }
101  }
102  for (const key of Object.keys(objB)) {
103    if (objA[key] !== objB[key]) {
104      return false;
105    }
106  }
107  return true;
108}
109
110export function isString(s: unknown): s is string {
111  return typeof s === 'string' || s instanceof String;
112}
113
114// Given a string enum |enum|, check that |value| is a valid member of |enum|.
115export function isEnumValue<T extends {}>(
116  enm: T,
117  value: unknown,
118): value is T[keyof T] {
119  return Object.values(enm).includes(value);
120}
121