# Parser The parser is responsible for sanitizing and standardizing InnerTube responses while preserving the integrity of the data. ## Table of Contents - [Parser](#parser) - [Table of Contents](#table-of-contents) - [Structure](#structure) - [Core](#core) - [Clients](#clients) - [API](#api) - [`parse(data?: RawData, requireArray?: boolean, validTypes?: YTNodeConstructor | YTNodeConstructor[])`](#parsedata-rawdata-requirearray-boolean-validtypes-ytnodeconstructort--ytnodeconstructort) - [`parseResponse(data: IRawResponse): T`](#parseresponsedata-irawresponse-t) - [Usage](#usage) - [ObservedArray](#observedarray) - [SuperParsedResponse](#superparsedresponse) - [YTNode](#ytnode) - [Type Casting](#type-casting) - [Accessing properties without casting](#accessing-properties-without-casting) - [Memo](#memo) - [Adding new nodes](#adding-new-nodes) - [Generating nodes at runtime](#generating-nodes-at-runtime) - [How it works](#how-it-works) ## Structure ### Core * [`/types`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/types) - General response types. * [`/classes`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/classes) - InnerTube nodes. * [`generator.ts`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/generator.ts) - Used to generate missing nodes at runtime. * [`helpers.ts`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/helpers.ts) - Helper functions/classes for the parser. * [`parser.ts`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/parser.ts) - The core of the parser. * [`nodes.ts`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/nodes.ts) - Contains a list of all the InnerTube nodes, which is used to determine the appropriate node for a given renderer. It's important to note that this file is automatically generated and should not be edited manually. * [`misc.ts`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/misc.ts) - Miscellaneous classes. Also automatically generated. ### Clients The parser itself is not tied to any specific client. Therefore, we have a separate folder for each client that the library supports. These folders are responsible for arranging the parsed data into a format that can be easily consumed and understood. Additionally, the underlying data is also exposed for those who wish to access it. * [`/youtube`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/youtube) * [`/ytmusic`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/ytmusic) * [`/ytkids`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/ytkids) ## API * Parser * [.parse](#parse) * [.parseItem](#parse) * [.parseArray](#parse) * [.parseResponse](#parseresponse) ### `parse(data?: RawData, requireArray?: boolean, validTypes?: YTNodeConstructor | YTNodeConstructor[])` Responsible for parsing individual nodes. | Param | Type | Description | | --- | --- | --- | | data | `RawData` | The data to parse | | requireArray | `?boolean` | Whether the response should be an array | | validTypes | `YTNodeConstructor \| YTNodeConstructor[] \| undefined` | Types of `YTNode` allowed | - If `requireArray` is `true`, the response will be an `ObservedArray`. - If `validTypes` is `undefined`, the response will be an array of YTNodes. - If `validTypes` is an array, the response will be an array of YTNodes that are of the types specified in the array. - If `validTypes` is a single type, the response will be an array of YTNodes that are of the type specified. If you do not specify `requireArray`, the return type of the function will not be known at runtime. Therefore, to gain access to the response, we return it wrapped in a helper, `SuperParsedResponse`. You may use the `Parser#parseArray` and `Parser#parseItem` methods to parse the response in a deterministic way. ### `parseResponse(data: IRawResponse): T` Unlike `parse`, this can be used to parse the entire response object. | Param | Type | Description | | --- | --- | --- | | data | `IRawResponse` | Raw InnerTube response | ## Usage ### ObservedArray You can utilize an `ObservedArray` as a regular array, but it also offers further methods for accessing and casting values in a type-safe manner. ```ts // For example, we have a feed, and want all the videos: const feed = new ObservedArray([...feed.contents]); // Here, we use the filterType method to retrieve only GridVideo items from the feed. const videos = feed.filterType(GridVideo); // `videos` is now a GridVideo[] array. // Alternatively, we can use firstOfType to retrieve the first GridVideo item from the feed. const firstVideo = feed.firstOfType(GridVideo); // If we want to make sure that all elements in the `feed` array are of the `GridVideo` type, we can use the `as` method to cast the entire array to a `GridVideo[]` type. If the cast fails because of non-GridVideo items, an exception is thrown. const allVideos = feed.as(GridVideo); // Note that ObservedArray provides additional methods beyond what's shown here, which we use internally. For more information, see the source code or documentation. ``` ### SuperParsedResponse Represents a parsed response in an unknown state. Either a `YTNode`, an `ObservedArray`, or `null`. To extract the actual value, you must first assert the type and unwrap the response. ```ts // First, parse the data and store it in `response`. const response = Parser.parse(data); // Check whether `response` is a YTNode. if (response.is_item) { // If so, we can assert that it is a YTNode and retrieve it. const node = response.item(); } // Check whether `response` is an ObservedArray. if (response.is_array) { // If so, we can assert that it is an ObservedArray and retrieve its contents as an array of YTNode objects. const nodes = response.array(); } // Finally, to check if `response` is a null value, use the `is_null` getter. const is_null = response.is_null; ``` ### YTNode All renderers returned by InnerTube are converted to this generic class and then extended for the specific renderers. This class is what allows us a type-safe way to use data returned by the InnerTube API. Here's how to use this class to access returned data: ### Type Casting ```ts // We can cast a YTNode to a child class of YTNode const results = node.as(TwoColumnSearchResults); // This will throw an error if the node is not a TwoColumnSearchResults. // Therefore, we may want to check for the type of the node before casting. if (node.is(TwoColumnSearchResults)) { // We do not need to recast the node; it is already a TwoColumnSearchResults after calling is() and using it in the branch where is() returns true. const results = node; } // Sometimes we can expect multiple types of nodes, we can just pass all possible types as params. const results = node.as(TwoColumnSearchResults, VideoList); // The type of `results` will now be `TwoColumnSearchResults | VideoList` // Similarly, we can check if the node is of a certain type. if (node.is(TwoColumnSearchResults, VideoList)) { // // Again, no casting is needed; the node is already of the correct type. const results = node; } ``` ### Accessing properties without casting Sometimes multiple nodes have the same properties, and we don't want to check the type of the node before accessing the property. For example, the property 'contents' is used by many node types, and we may add more in the future. As such, we want to only assert the property instead of casting to a specific type. ```ts // Accessing a property on a node when you aren't sure if it exists. const prop = node.key("contents"); // This returns the value wrapped into a `Maybe` type, which you can use to determine the type of the value. // However, this throws an error if the key doesn't exist, so we may want to check for the key before accessing it. if (node.hasKey("contents")) { const prop = node.key("contents"); } // We can assert the type of the value. const prop = node.key("contents"); if (prop.isString()) { const value = prop.string(); } // We can do more complex assertions, like checking for instanceof. const prop = node.key("contents"); if (prop.isInstanceOf(Text)) { const text = prop.instanceOf(Text); // Then use the value as the given type. text.runs.forEach(run => { console.log(run.text); }); } // There are special methods for use with the parser, such as getting the value as a YTNode. const prop = node.key("contents"); if (prop.isNode()) { const node = prop.node(); } // Like with YTNode, keys can also be checked for YTNode child class types. const prop = node.key("contents"); if (prop.isNodeOfType(TwoColumnSearchResults)) { const results = prop.nodeOfType(TwoColumnSearchResults); } // Or we can check for multiple types of nodes. const prop = node.key("contents"); if (prop.isNodeOfType([TwoColumnSearchResults, VideoList])) { const results = prop.nodeOfType([TwoColumnSearchResults, VideoList]); } // Sometimes an ObservedArray is returned when working with parsed data. // We also have a helper for this. const prop = node.key("contents"); if (prop.isObserved()) { const array = prop.observed(); // Now we can use all the ObservedArray methods as normal, such as finding nodes of a certain type. const results = array.filterType(GridVideo); } // Other times a SuperParsedResult is returned, like when using the `Parser#parse` method. const prop = node.key("contents"); if (prop.isParsed()) { const result = prop.parsed(); // SuperParsedResult is another helper for type-safe access to the parsed data. // It is explained above with the `Parser#parse` method. const results = results.array(); const videos = results.filterType(Video); } // Sometimes we just want to debug something and are not interested in finding the type. // This will, however, warn you when being used. const prop = node.key("contents"); const value = prop.any(); // Arrays are a special case, as every element may be of a different type. // The `arrayOfMaybe` method will return an array of `Maybe`s. const prop = node.key("contents"); if (prop.isArray()) { const array = prop.arrayOfMaybe(); // This will return `Maybe[]`. } // Or, if you don't need type safety, you can use the `array` method. const prop = node.key("contents"); if (prop.isArray()) { const array = prop.array(); // This will return any[]. } ``` ### Memo The `Memo` class is a helper class for memoizing values in the `Parser#parseResponse` method. It can be used to conveniently access nodes after parsing the response. For example, if we'd like to obtain all of the videos from a search result, we can use the `Memo#getType` method to find them quickly without needing to traverse the entire response. ```ts const response = Parser.parseResponse(data); const videos = response.contents_memo.getType(Video); // This returns the nodes as an `ObservedArray