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