<!DOCTYPE html>
<html>
  <head>
    <title>Angular 2 Material Plunker</title>
    <link rel="stylesheet" href="https://npmcdn.com/codemirror@latest/lib/codemirror.css" />
    
    <!-- Load common libraries -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/2.1.1/typescript.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.4.1/core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.7.2/zone.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.41/system.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
    
    <!-- Configure SystemJS -->
    <script src="systemjs.config.js"></script>

    <script>
      System
        .import('main.ts')
        .catch(console.error.bind(console));
    </script>
    
    <!-- Load the Angular Material 2 stylesheet -->
    <link href="https://rawgit.com/angular/material2-builds/master/core/theming/prebuilt/deeppurple-amber.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link href="app.css" rel='stylesheet' type='text/css'>
    <style>body { font-family: Roboto, Arial, sans-serif; }</style>

  </head>

  <body>
    <material-app>Loading the Angular 2 JSON Flatten Tool</material-app>
  </body>

</html>

<!--
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->
import {Component} from '@angular/core';
import {Http} from '@angular/http'
import {bootstrap} from '@angular/platform-browser-dynamic';

import { flatten } from './utils/flatten.util';
import { unflatten } from './utils/unflatten.util';

import { unflatSample, flatSample } from './samples/demos';
import prettyJson from 'json-stringify-pretty-compact';

const PlaceholderMessages = {
  FLAT: 'Paste a json to flatten it',
  UNFLAT: 'Paste a json to unflatten it'
}
@Component({
  selector: 'material-app',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  
  isFlat: boolean = false;
  jsonData: string = '';
  editorConfig = {
    lineNumbers: true,
    mode: { name: "javascript", json: true }
  };
  data = '';
  codeMessagePlaceholder = PlaceholderMessages.FLAT;

  constructor(http: Http) {
    this.jsonData = prettyJson(unflatSample, { indent: 2});
  }
  
  updateJson(jsonData: string) {
    this.jsonData = jsonData;
  }
  
  flatten() {
    const json = JSON.parse(this.jsonData.trim());
    if (json) {
      this.jsonData = this.formatJson(flatten(json));
      this.changeMode();
    }
  }
  
  unflatten() {
    const json = JSON.parse(this.jsonData.trim());
    if (json) {
      this.jsonData = this.formatJson(unflatten(json));
      this.changeMode();
    }
  }
  
  loadUnflatSample() {
    this.jsonData = this.formatJson(unflatSample);
  }
  
  loadFlatSample() {
    this.jsonData = this.formatJson(flatSample);
  }
  
  formatJson(_json) {
    return prettyJson(_json, { indent: 2 });
  }
  
  handleChangeMode() {
    this.changeMode();
    this.clearJson();
  }

  changeMode() {
    this.isFlat = !this.isFlat;
    this.codeMessagePlaceholder = this.isFlat
      ? PlaceholderMessages.UNFLAT
      : PlaceholderMessages.FLAT;
  }
  
  clearJson() {
    this.jsonData = '';
  }
}

/*
 Copyright 2016 Google Inc. All Rights Reserved.
 Use of this source code is governed by an MIT-style license that
 can be found in the LICENSE file at http://angular.io/license
 */
<md-toolbar color="primary">
  JSON Flatten Tool
</md-toolbar>

<div>
  <md-toolbar>
  <md-slide-toggle
    [checked]="!isFlat"
    (change)="handleChangeMode()">
    To Flat Mode
  </md-slide-toggle>

  <md-toolbar-row>
    <button color="secondary" md-raised-button
      [disabled]="isFlat"
      (click)="loadUnflatSample()">
      Sample unflattened JSON
    </button>
    <button color="secondary" md-raised-button
      [disabled]="!isFlat"
      (click)="loadFlatSample()">
      Sample flattened JSON
    </button>
  </md-toolbar-row>
  
  <md-toolbar-row>
    <button color="primary" md-raised-button
      *ngIf="isFlat"
      (click)="unflatten()">
      Unflatten
    </button>
    <button color="primary" md-raised-button
      *ngIf="!isFlat"
      (click)="flatten()">
      Flatten
    </button>
    <span class="spacer"></span>
    <button color="warn" md-raised-button
      mdTooltip="clears the json code..."
      (click)="clearJson()">
      <md-icon>delete sweep</md-icon>
    </button>
  </md-toolbar-row>
</md-toolbar>
  
  <p>
    <md-input-container class="full-width">
      <textarea mdTextareaAutosize mdInput
        [placeholder]="codeMessagePlaceholder"
        minRows="30"
        #jsonArea
        [value]="jsonData"
        (input)="updateJson(jsonArea.value)"></textarea>
    </md-input-container>
  </p>

</div>

<!--
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->
/** Add Transpiler for Typescript */
System.config({
  transpiler: 'typescript',
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  packages: {
    '.': {
      defaultExtension: 'ts'
    },
    'vendor': {
      defaultExtension: 'js'
    }
  }
});

System.config({
  map: {
    'main': 'main.js',
    
    // Angular specific mappings.
    '@angular/core': 'https://unpkg.com/@angular/core/bundles/core.umd.js',
    '@angular/common': 'https://unpkg.com/@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'https://unpkg.com/@angular/compiler/bundles/compiler.umd.js',
    '@angular/http': 'https://unpkg.com/@angular/http/bundles/http.umd.js',
    '@angular/forms': 'https://unpkg.com/@angular/forms/bundles/forms.umd.js',
    '@angular/router': 'https://unpkg.com/@angular/router/bundles/router.umd.js',
    '@angular/platform-browser': 'https://unpkg.com/@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'https://unpkg.com/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/material': 'https://rawgit.com/angular/material2-builds/master/bundles/material.umd.js',
    
    // Rxjs mapping
    'rxjs': 'https://unpkg.com/rxjs',
    'json-stringify-pretty-compact': 'https://unpkg.com/json-stringify-pretty-compact@1.0.2'
  },
  packages: {
    // Thirdparty barrels.
    'rxjs': { main: 'index' }
  }
});

/*
 Copyright 2016 Google Inc. All Rights Reserved.
 Use of this source code is governed by an MIT-style license that
 can be found in the LICENSE file at http://angular.io/license
 */
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppComponent} from './app.component';
import {MaterialModule} from '@angular/material';

@NgModule({

  imports: [
    BrowserModule,
    CommonModule,
    MaterialModule.forRoot()
  ],

  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: []
})
export class PlunkerAppModule {}

platformBrowserDynamic().bootstrapModule(PlunkerAppModule);

/*
 Copyright 2016 Google Inc. All Rights Reserved.
 Use of this source code is governed by an MIT-style license that
 can be found in the LICENSE file at http://angular.io/license
 */
.full-width {
  width: 100%;
}

.spacer {
  flex: 1 1 auto;
}
export function flatten(_json: any) {
  function objToJson (key, obj) {
    return Object.keys(obj).reduce((acc, current, index) => {
      const _key = '' !== key ? `${key}.${current}` : `${current}`;
      const currentValue = obj[current];
      if (Array.isArray(currentValue) || Object(currentValue) === currentValue) {
        Object.assign(acc, objToJson(_key, currentValue));
      } else {
        acc[_key] = currentValue;
      }
      return acc;
    }, {});
  };
  return objToJson('', _json);
};
export function unflatten(_json: any) {
  function jsonToObj(data: any, result) {
    return Object.keys(data).reduce((acc, current, index) => {
      const inlineKeys = current.split('.');
      let firstProp = inlineKeys.shift();
      const hasProps = inlineKeys.length >= 1;
      if (hasProps) {
        const parsedKey = parseInt(inlineKeys[0], 10);
        const isNextKeyNumber = !isNaN(parsedKey);
        let _nextData = {};
        if (!acc[firstProp]) {
          acc[firstProp] = isNextKeyNumber ? [] : {};
        }
        if (isNextKeyNumber) {
          const _index = parseInt(inlineKeys.shift(), 10);
          const isValueInArray = acc[firstProp].length - 1 >= _index;
          const currentValueObj = acc[firstProp][_index];
          _nextData[inlineKeys.join('.')] = data[current];
          acc[firstProp][_index] = isValueInArray
            ? Object.assign(currentValueObj, jsonToObj(_nextData, currentValueObj))
            : jsonToObj(_nextData, {});
        } else {
          _nextData[inlineKeys.join('.')] = data[current];
          Object.assign(acc[firstProp], jsonToObj(_nextData, acc[firstProp]));
        }
      } else {
        acc[firstProp] = data[current];
      }
      return acc;
    }, result);
  }
  return jsonToObj(_json, {});
};
export let unflatSample = {
  "firstName": "John",
  "lastName": "Green",
  "car": {
    "make": "Honda",
    "model": "Civic",
    "revisions": [
      {
        "miles": 10150,
        "code": "REV01"
      },
      {
        "miles": 20021,
        "code": "REV02",
        "changes": [
          {
            "type": "asthetic",
            "desc": "Left tire cap"
          },
          {
            "type": "mechanic",
            "desc": "Engine pressure regulator"
          }
        ]
      }
    ]
  },
  "visits": [
    {
      "date": "2015-01-01",
      "dealer": "DEAL-001"
    },
    {
      "date": "2015-03-01",
      "dealer": "DEAL-002"
    }
  ]
};

export let flatSample = {
  "firstName": "John",
  "lastName": "Green",
  "car.make": "Honda",
  "car.model": "Civic",
  "car.revisions.0.miles": 10150,
  "car.revisions.0.code": "REV01",
  "car.revisions.1.miles": 20021,
  "car.revisions.1.code": "REV02",
  "car.revisions.1.changes.0.type": "asthetic",
  "car.revisions.1.changes.0.desc": "Left tire cap",
  "car.revisions.1.changes.1.type": "mechanic",
  "car.revisions.1.changes.1.desc": "Engine pressure regulator",
  "visits.0.date": "2015-01-01",
  "visits.0.dealer": "DEAL-001",
  "visits.1.date": "2015-03-01",
  "visits.1.dealer": "DEAL-002"
};