165

Trying to write an Angular 2 pipe that will take a JSON object string and return it pretty-printed/formatted to display to the user.

For example, it would take this:

{ "id": 1, "number": "K3483483344", "state": "CA", "active": true }

And return something that looks like this when displayed in HTML:

enter image description here

So in my view I could have something like:

<td> {{ record.jsonData | prettyprint }} </td>
Derek
  • 5,137
  • 9
  • 28
  • 39
  • 1
    looks like you want to create a custom pipe. here's some documentation: https://angular.io/docs/ts/latest/guide/pipes.html#!#custom-pipes. try it out and if you run into issues feel free to post a more specific question – danyamachine May 18 '16 at 19:26

5 Answers5

475

I would like to add an even simpler way to do this, using the built-in json pipe:

<pre>{{data | json}}</pre>

This way, the formatting is preserved.

Shane Hsu
  • 7,937
  • 6
  • 39
  • 63
  • 12
    Cool. Note that the formatting requires `
    ` and won't work with e.g. `

    `,``, etc.

    – Jeppe Aug 22 '20 at 12:27
  • Also, if your json is nested and not formatting, update your css to have pre element with white-space: pre-wrap – rleffler Nov 04 '22 at 16:15
32

I would create a custom pipe for this:

@Pipe({
  name: 'prettyprint'
})
export class PrettyPrintPipe implements PipeTransform {
  transform(val) {
    return JSON.stringify(val, null, 2)
      .replace(' ', '&nbsp;')
      .replace('\n', '<br/>');
  }
}

and use it this way:

@Component({
  selector: 'my-app',
  template: `
    <div [innerHTML]="obj | prettyprint"></div>
  `,
  pipes: [ PrettyPrintPipe ]
})
export class AppComponent {
  obj = {
    test: 'testttt',
    name: 'nameeee'
  }
}

See this stackblitz: https://stackblitz.com/edit/angular-prettyprint

Philipp Kief
  • 8,172
  • 5
  • 33
  • 43
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Worked! I had a json string, not a json object, so I just had to add this line before calling JSON.stringify: var jsonObject = JSON.parse(jsonString); – Derek May 18 '16 at 21:50
  • Unexpected syntax, I expect use to be more like using a standard pipe: `
    {{obj | prettyprint }}
    ` but it worked!
    – Paul Gorbas Jun 28 '16 at 19:34
  • 1
    As @shane-hsu points out, there is a built-in `json` pipe, as there is in Angular 1. – David Souther Aug 02 '16 at 13:43
  • Your missing `implements PipeTransform` after `export class PrettyPrintPipe` – Matthias Sommer Aug 29 '17 at 09:33
  • 1
    Why is this not the accepted anser? It answers the question asked, unlike the accepted answer – davejoem Jan 10 '18 at 11:30
  • .replace() only replaces the first instance of the matched string. Should use `.replace(/ /g, ' ').replace(/\n/g, '
    ');` instead
    – Balage Apr 16 '21 at 17:45
30

I had required this scenario and many times require it. I saw this question is still trending in 2021. So I created a detailed post explaining not how to just prettify it but add colors to it and built a small tool to play around with.

2021+ solution: I built my own custom version of pipe (inspried by this answer) which not only prettifies but also adds colors to JSON like vscode. I don't use a built-in JSON pipe because it doesn't serve my full purpose.

This also gives you the freedom to add number lines and padding if you wish to.

Sample output like below

Demo

global stylesheet should contain colors as per your theme for e.g styles.scss

pre {
  font-weight: 400;

  .number-line {
    color: #adadaf;
  }
  .string {
    color: #95c602;
  }
  .number {
    color: #f2b619;
  }
  .boolean {
    color: #0097f1;
  }
  .null {
    color: #727990;
  }
  .key {
    color: #fff;
  }
}

Source code of the pipe

@Pipe({
  name: 'prettyjson',
  pure:true
})
export class PrettyJsonPipe implements PipeTransform {
  transform(value: any, args: any[]): any {
    try {
      /**
       * check and try to parse value if it's not an object
       * if it fails to parse which means it is an invalid JSON
       */
      return this.applyColors(
        typeof value === 'object' ? value : JSON.parse(value),
        args[0],
        args[1]
      );
    } catch (e) {
      return this.applyColors({ error: 'Invalid JSON' }, args[0], args[1]);
    }
  }

  applyColors(obj: any, showNumebrLine: boolean = false, padding: number = 4) {
    // line number start from 1
    let line = 1;

    if (typeof obj != 'string') {
      obj = JSON.stringify(obj, undefined, 3);
    }

    /**
     * Converts special charaters like &, <, > to equivalent HTML code of it
     */
    obj = obj.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    /* taken from https://stackoverflow.com/a/7220510 */

    /**
     * wraps every datatype, key for e.g
     * numbers from json object to something like
     * <span class="number" > 234 </span>
     * this is why needed custom themeClass which we created in _global.css
     * @return final bunch of span tags after all conversion
     */
    obj = obj.replace(
      /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
      (match: any) => {
        // class to be applied inside pre tag
        let themeClass = 'number';
        if (/^"/.test(match)) {
          if (/:$/.test(match)) {
            themeClass = 'key';
          } else {
            themeClass = 'string';
          }
        } else if (/true|false/.test(match)) {
          themeClass = 'boolean';
        } else if (/null/.test(match)) {
          themeClass = 'null';
        }
        return '<span class="' + themeClass + '">' + match + '</span>';
      }
    );

    /**
     * Regex for the start of the line, insert a number-line themeClass tag before each line
     */
    return showNumebrLine
      ? obj.replace(
          /^/gm,
          () =>
            `<span class="number-line pl-3 select-none" >${String(line++).padEnd(padding)}</span>`
        )
      : obj;
  }
}

now pass these params inside HTML like this. If you don't pass it by default value of showNumberline is false and padding is 4

<pre [innerHTML]="dummyJsonObject | prettyjson: [true, 3]"></pre>

Hope this helps.

minigeek
  • 2,766
  • 1
  • 25
  • 35
  • this is great but this is not scalable as the rendering and scrolling gets very slow if the json is huge, is there any option to apply virtualisation to this? – Atin Singh Feb 14 '23 at 06:40
  • @AtinSingh tried it on large ones, so far it worked fine, but huge jsons will suffer since its a lot of string manupulation. you might have to add an option to collapse and open keys for key pais in json. would suggest to use an extension like jsoneditor in readonly mode. – minigeek Feb 15 '23 at 06:13
12

As this is the first result on google, let me add a quick sum up:

  • if you only need to print JSON without proper formatting, the build-in json pipe suggested by Shane Hsu works perfectly: <pre>{{data | json}}</pre>

  • however, if you want to have a different output, you will need to create your own pipe as Thierry Templier suggested:

  1. ng generate pipe prettyjson
  2. in prettyjson.pipe.ts:
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'prettyjson'
})
export class PrettyjsonPipe implements PipeTransform {

  transform(value: any, ...args: any[]): any {
    return JSON.stringify(value, null, 2)
    .replace(/ /g, '&nbsp;') // note the usage of `/ /g` instead of `' '` in order to replace all occurences
    .replace(/\n/g, '<br/>'); // same here
  }

}
  1. Finally, and because we return HTML content, the pipe must be used inside an innerHTML function:
<div [innerHTML]="data | prettyjson"></div>
Tim
  • 3,910
  • 8
  • 45
  • 80
Dlacreme
  • 196
  • 1
  • 8
5

since my variable is two way binded with ngModel, I could not do it on html. I used on component side JSON.stringify(displayValue, null, 2) and it did the job.

Feng Zhang
  • 1,698
  • 1
  • 17
  • 20