### File

`src/lib/json-viewer/json-viewer.component.ts`

### Implements

`OnInit` `OnChanges`

### Metadata

changeDetection | `ChangeDetectionStrategy.OnPush`
host | `{ }`
selector | `rxap-json-viewer`
standalone | `true`
imports | `NgFor` `NgClass` `NgIf` `forwardRef(() => JsonViewerComponent)`
styleUrls | `./json-viewer.component.scss`
templateUrl | `./json-viewer.component.html`

### Index

###### Properties
Public cdr Public ignoredProperties Public ignoredTypes Public inspectValue Public segments Public subscriptions
###### Methods
Public buildSegments Public cleanObject Public isExpandable Public setInspectValue Public toggle
###### Inputs
expanded json

### Constructor

`constructor(ignoredProperties: Array, ignoredTypes: Array, cdr: ChangeDetectorRef)`
Defined in src/lib/json-viewer/json-viewer.component.ts:56
Parameters :
Name | Type | Optional
ignoredProperties | `Array<string | RegExp | Array>` | No
ignoredTypes | `Array<any | []>` | No
cdr | `ChangeDetectorRef` | No

### Inputs

expanded
Type : `boolean`
Default value : `false`
Defined in src/lib/json-viewer/json-viewer.component.ts:47

json
Type : `object | Record<string | number | symbol | >`
Default value : `{}`
Defined in src/lib/json-viewer/json-viewer.component.ts:46

### Methods

Public buildSegments
`buildSegments(inspectValue: any)`
Defined in src/lib/json-viewer/json-viewer.component.ts:139
Parameters :
Name | Type | Optional
inspectValue | `any` | No
Returns : `void`

Public cleanObject
`cleanObject(obj: any, stack: any[])`
Defined in src/lib/json-viewer/json-viewer.component.ts:91
Parameters :
Name | Type | Optional | Default value
obj | `any` | No |
stack | `any[]` | No | `[]`
Returns : `any`

Public isExpandable
`isExpandable(segment: Segment)`
Defined in src/lib/json-viewer/json-viewer.component.ts:151
Parameters :
Name | Type | Optional
segment | `Segment` | No
Returns : `boolean`

Public setInspectValue
`setInspectValue(json: any)`
Defined in src/lib/json-viewer/json-viewer.component.ts:126
Parameters :
Name | Type | Optional
json | `any` | No
Returns : `void`

Public toggle
`toggle(segment: Segment)`
Defined in src/lib/json-viewer/json-viewer.component.ts:155
Parameters :
Name | Type | Optional
segment | `Segment` | No
Returns : `void`

### Properties

Public cdr
Type : `ChangeDetectorRef`
Decorators : `@Inject(ChangeDetectorRef)`
Defined in src/lib/json-viewer/json-viewer.component.ts:61

Public ignoredProperties
Type : `Array<string | RegExp>`
Default value : `[]`
Defined in src/lib/json-viewer/json-viewer.component.ts:51

Public ignoredTypes
Type : `any[]`
Default value : `[]`
Defined in src/lib/json-viewer/json-viewer.component.ts:52

Public inspectValue
Type : `any`
Defined in src/lib/json-viewer/json-viewer.component.ts:49

Public segments
Type : `Segment[]`
Default value : `[]`
Defined in src/lib/json-viewer/json-viewer.component.ts:56

Public subscriptions
Default value : `new Subscription()`
Defined in src/lib/json-viewer/json-viewer.component.ts:54

    import {
      NgClass,
      NgFor,
      NgIf,
    } from '@angular/common';
    import {
      ChangeDetectionStrategy,
      ChangeDetectorRef,
      Component,
      forwardRef,
      Inject,
      Input,
      isDevMode,
      OnChanges,
      OnInit,
      SimpleChanges,
    } from '@angular/core';
    import { isTeardownLogic } from '@rxap/rxjs';
    import { isPromiseLike } from '@rxap/utilities';
    import { Subscription } from 'rxjs';
    import {
      JSON_VIEW_IGNORED_PROPERTIES,
      JSON_VIEW_IGNORED_TYPES,
    } from './json-viewer.tokens';

    export interface Segment {
      key: string;
      value: any;
      type: undefined | string;
      description: string;
      expanded: boolean;
    }

    @Component({
      selector: 'rxap-json-viewer',
      templateUrl: './json-viewer.component.html',
      styleUrls: [ './json-viewer.component.scss' ],
      changeDetection: ChangeDetectionStrategy.OnPush, // eslint-disable-next-line @angular-eslint/no-host-metadata-property
      host: { class: 'rxap-json-viewer' },
      standalone: true,
      imports: [ NgFor, NgClass, NgIf, forwardRef(() => JsonViewerComponent) ],
    })
    export class JsonViewerComponent implements OnInit, OnChanges {

      // TODO : add object input. The json input will not be cleaned
      @Input() public json: object | Record<string | number | symbol, unknown> = {};
      @Input() public expanded = false;

      public inspectValue: any;

      public ignoredProperties: Array<string | RegExp> = [];
      public ignoredTypes: any[] = [];

      public subscriptions = new Subscription();

      public segments: Segment[] = [];

      constructor(
        @Inject(JSON_VIEW_IGNORED_PROPERTIES) ignoredProperties: Array<string | RegExp | Array<string | RegExp>>,
        @Inject(JSON_VIEW_IGNORED_TYPES) ignoredTypes: Array<any | any[]>,
        @Inject(ChangeDetectorRef) public cdr: ChangeDetectorRef,
      ) {
        function flat(acc: any[], cur: any | any[]): any[] {
          if (Array.isArray(cur)) {
            acc.push(...cur.reduce(flat, []));
          } else {
            acc.push(cur);
          }

          return acc;
        }

        this.ignoredProperties = ignoredProperties.reduce(flat, []);
        this.ignoredTypes = ignoredTypes.reduce(flat, []);
      }

      public ngOnInit() {
        this.setInspectValue(this.json);
      }

      public ngOnChanges(changes: SimpleChanges): void {
        const jsonChange = changes['json'];
        if (jsonChange) {
          if (isDevMode()) {
            console.debug('json change');
          }
          this.setInspectValue(jsonChange.currentValue);
        }
      }

      public cleanObject(obj: any, stack: any[] = []): any {
        if (typeof obj === 'string') {
          return obj;
        }
        if (stack.some(item => item === obj)) {
          return '__circular__';
        }
        stack = stack.slice();
        stack.unshift(obj);
        return Object
          .entries(obj)
          .filter(([ key, value ]) => !isPromiseLike(value))
          .filter(([ key, value ]) => !isTeardownLogic(value))
          .filter(([ key, value ]) => this.ignoredProperties.every(ip => typeof ip === 'string' ?
            key !== ip :
            !key.match(ip)))
          .filter(([ key, value ]) => [ ...this.ignoredTypes ].every(type => !(value instanceof type)))
          .map(([ key, value ]) => {
            if (value && typeof value === 'object') {
              if (Array.isArray(value)) {
                return [ key, value.map(item => this.cleanObject(item, stack)) ];
              }
              if (typeof value === 'object') {
                return [ key, this.cleanObject(value, stack) ];
              }
            }
            return [ key, value ];
          })
          .sort((a, b) => a[0].localeCompare(b[0]))
          .reduce((controlView, [ key, value ]) => ({
            ...controlView,
            [key]: value,
          }), {});
      }

      public setInspectValue(json: any): void {
        if (typeof json !== 'object' || json === null || json === undefined) {
          console.warn('The inspection value is not an object!');
        } else {
          if (typeof json['toJSON'] === 'function') {
            json = json.toJSON();
          }
          this.inspectValue = this.cleanObject(structuredClone(json));
          this.buildSegments(this.inspectValue);
          this.cdr.markForCheck();
        }
      }

      public buildSegments(inspectValue: any) {
        this.segments = [];

        if (typeof inspectValue === 'object') {
          Object.keys(inspectValue).forEach(key => {
            this.segments.push(this.parseKeyValue(key, inspectValue[key]));
          });
        } else {
          this.segments.push(this.parseKeyValue(`(${ typeof inspectValue })`, inspectValue));
        }
      }

      public isExpandable(segment: Segment) {
        return segment.type === 'object' || segment.type === 'array';
      }

      public toggle(segment: Segment) {
        if (this.isExpandable(segment)) {
          segment.expanded = !segment.expanded;
        }
      }

      private parseKeyValue(key: any, value: any): Segment {
        const segment: Segment = {
          key: key,
          value: value,
          type: undefined,
          description: '' + value,
          expanded: this.expanded,
        };

        switch (typeof segment.value) {
          case 'number': {
            segment.type = 'number';
            break;
          }
          case 'boolean': {
            segment.type = 'boolean';
            break;
          }
          case 'function': {
            segment.type = 'function';
            break;
          }
          case 'string': {
            segment.type = 'string';
            segment.description = '"' + segment.value + '"';
            break;
          }
          case 'undefined': {
            segment.type = 'undefined';
            segment.description = 'undefined';
            break;
          }
          case 'object': {
            // yea, null is object
            if (segment.value === null) {
              segment.type = 'null';
              segment.description = 'null';
            } else if (Array.isArray(segment.value)) {
              segment.type = 'array';
              segment.description = 'Array[' + segment.value.length + '] ' + JSON.stringify(segment.value);
            } else if (segment.value instanceof Date) {
              segment.type = 'date';
            } else {
              segment.type = 'object';
              segment.description = 'Object ' + JSON.stringify(segment.value);
            }
            break;
          }
        }

        return segment;
      }

    }

    <section class="rxap-container">
      <section
        *ngFor="let segment of segments"
        [ngClass]="['segment', 'segment-type-' + segment.type]">
        <section
          (click)="toggle(segment)"
          [ngClass]="{
            'segment-main': true,
            'expandable': isExpandable(segment),
            'expanded': segment.expanded
          }">
          <div *ngIf="isExpandable(segment)" class="toggler"></div>
          <span class="segment-key text-primary">{{ segment.key }}</span>
          <span class="segment-separator">: </span>
          <span *ngIf="!segment.expanded || !isExpandable(segment)"
                class="segment-value text-white dark:text-black">{{ segment.description }}</span>
        </section>
        <section *ngIf="segment.expanded && isExpandable(segment)" class="children">
          <rxap-json-viewer [expanded]="expanded" [json]="segment.value"></rxap-json-viewer>
        </section>
      </section>
    </section>

`./json-viewer.component.scss`

    $type-colors: (
      string: var(--json-string-color, #FF6B6B),
      number: var(--json-number-color, #009688),
      boolean: var(--json-boolean-color, #b938a4),
      date: var(--json-date-color, #05668D),
      array: var(--json-array-color, #999),
      object: var(--json-object-color, #999),
      function: var(--json-function-color, #999),
      'null': var(--json-null-color, #fff),
      undefined: var(--json-undefined-color, #fff)
    );

    .rxap-container {

      width: 100%;
      height: 100%;
      overflow: hidden;
      position: relative;

      font-family: monospace;
      font-size: 16px;

      .segment {

        .segment-main {

          .toggler {
            font-size: 8px;
            line-height: 12px;
          }

        }

      }

      .segment {
        padding: 2px;
        margin: 1px 1px 1px 12px;

        .segment-main {
          word-wrap: break-word;

          .toggler {
            position: absolute;
            margin-left: -14px;
            margin-top: 3px;
            vertical-align: middle;

            &::after {
              display: inline-block;
              content: "►";
              transition: transform 0.1s ease-in;
            }
          }

        }

        .children {
          margin-left: 12px;
        }
      }

      .segment-type-object > .segment-main,
      .segment-type-array > .segment-main {
        white-space: nowrap;
      }

      .expanded > .toggler::after {
        transform: rotate(90deg);
      }

      .expandable,
      .expandable > .toggler {
        cursor: pointer;
      }

      .segment {

        .segment-main {

          .toggler {
            color: #787878;

          }

          .segment-separator {
            color: #999;
          }

        }

      }

      @each $type, $color in $type-colors {
        .segment-type-#{$type} > .segment-main > .segment-value {
          color: $color;
        }
      }

      // special cases need highlightted
      .segment-type-null > .segment-main > .segment-value {
        background-color: var(--warn-500, darkred);
      }

      .segment-type-undefined > .segment-main {
        & > .segment-key {
          color: #999;
        }

        & > .segment-value {
          background-color: #999;
        }
      }

    }

Legend
Html element
Component
Html element with directive

# results matching ""

# No results matching ""