Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validating JSON is not working #62

Closed
bsivanov opened this issue Jun 25, 2021 · 7 comments
Closed

Validating JSON is not working #62

bsivanov opened this issue Jun 25, 2021 · 7 comments

Comments

@bsivanov
Copy link
Contributor

In order to use monaco-editor as JSON editor which validates the content of the JSON against a schema, we need a to call monaco.languages.json.jsonDefaults.setDiagnosticsOptions, e.g. like in the Configure JSON defaults example in the monaco editor playground.

It seems there is no good place to call this method though - if I correctly get the API, the only place where this could be done (where the monaco code will be loaded) is in the editorChange event handler:

import { Component, OnInit } from '@angular/core';
import * as monaco from "monaco-editor";

@Component({
  selector: 'app-root',
  styleUrls: ['./app.component.css'],
  template:`
  <ng-monaco-editor 
  style="height: 300px" 
  [modelUri]="modelUri"
  [(ngModel)]="jsonCode" 
  [options]="options"
  (editorChange)="editorChange($event)"
  ></ng-monaco-editor>`,
})
export class AppComponent {
    jsonCode = [
    '{',
    '    "p1": "v3",',
    '    "p2": false',
    "}"
  ].join('\n');

  options = {
    language: "json"
  };

  modelUri = "a://b/foo.json";

  editorChange(editor: monaco.editor.IStandaloneCodeEditor): void {
    monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
      validate: true,
      schemas: [{
        uri: "http://myserver/foo-schema.json",
        fileMatch: [this.modelUri],
        schema: {
          type: "object",
          properties: {
            p1: {
              enum: ["v1", "v2"]
            },
            p2: {
              enum: ["x1", "x2"]
            }
          }
        }
      }],
    });
  }
}

but for some reason the editor is not validating against the schema. Am I calling it in the wrong place (I can see that similar libraries have exposed monacoLoad method or similar which is called when the module is fully loaded), or is there a better approach?

I prepared a runnable repo here, the problematic code is in app.component.ts.

@JounQin
Copy link
Member

JounQin commented Jun 25, 2021

Just a fast look, you should provide a custom MonacoProviderService like https://github.com/alauda/ng-monaco-editor#load-ng-monaco-editor-module

Just extend it and in initMonaco method:

async initMonaco() {
  const monaco = await super.initMonaco()
  // do what you want
  // don't forget to return `monaco`
  return monaco
}

@bsivanov
Copy link
Contributor Author

Thanks for the blazing fast response @JounQin! Indeed moving the code after initMonaco is working:

// app.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { MonacoEditorModule, MonacoProviderService } from 'ng-monaco-editor';

import { AppComponent } from './app.component';
import { CustomMonacoProviderService } from './CustomMonacoProviderService';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule, 
    FormsModule,
    MonacoEditorModule.forRoot({
      baseUrl: 'lib',
      defaultOptions: {},
    }),
  ],
  exports: [
    MonacoEditorModule
  ],
  providers: [
    {
      provide: MonacoProviderService,
      useClass: CustomMonacoProviderService,
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

and

import { MonacoProviderService } from "ng-monaco-editor";

export class CustomMonacoProviderService extends MonacoProviderService {
    async initMonaco() {
        const monaco = await super.initMonaco();
        
        monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
            validate: true,
            schemas: [{
              uri: "http://myserver/foo-schema.json",
              fileMatch: ["a://b/foo.json"],
              schema: {
                type: "object",
                properties: {
                  p1: {
                    enum: ["v1", "v2"]
                  },
                  p2: {
                    enum: ["x1", "x2"]
                  }
                }
              }
            }],
          });

        return monaco;
      }
}

@bsivanov
Copy link
Contributor Author

bsivanov commented Jun 29, 2021

After some further investigation, it seems that settings jsonDefaults diagnostics options indeed works in editorChange, but I am probably not importing the monaco object correctly.

So, (without the suggested custom loading), this works:

import { Component } from '@angular/core';
import * as monaco from "monaco-editor";

@Component({
  selector: 'app-root',
  styleUrls: ['./app.component.css'],
  template:`
  <ng-monaco-editor 
  style="height: 300px" 
  [modelUri]="modelUri"
  [(ngModel)]="jsonCode" 
  [options]="options"
  (editorChange)="editorChange($event)"
  ></ng-monaco-editor>`,
})
export class AppComponent {
  // ...

  editorChange(editor: monaco.editor.IStandaloneCodeEditor): void {
    (window as any).monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
      validate: true,
      schemas: [{
        uri: "http://myserver/foo-schema.json",
        fileMatch: [this.modelUri],
        schema: {
          type: "object",
          properties: {
            p1: {
              enum: ["v1", "v2"]
            },
            p2: {
              enum: ["x1", "x2"]
            }
          }
        }
      }],
    });
  }
}

Note that I obtain the monaco object from the window - if instead I try just monaco.languages.json.jsonDefaults.setDiagnosticsOptions it's not working. Do you think that this is related to this breaking change in monaco-editor v.0.22.0:

The ESM version of the editor will no longer define a global monaco object. You can define global.MonacoEnvironment = { globalAPI: true } if you want for the editor to define this global object.

Also there is a related change in monaco-editor-webpack-plugin: Add globalAPI option to expose the editor API through a global monaco object, which it seems is included in a newer version than the one currently used in this project.

@bsivanov bsivanov reopened this Jun 29, 2021
@JounQin
Copy link
Member

JounQin commented Jun 29, 2021

You should not load monaco-editor by yourself. You're mixing amd and esm version monaco at the same time.

If you want to use amd version, use window.monaco.

If you want to use esm version, use custom MonacoProviderService.

For typings like import * as monaco from "monaco-editor"; You should use import type { editor } from "monaco-editor"; to ensure not importing monaco-editor js codes.

@JounQin JounQin closed this as completed Jun 29, 2021
@JounQin
Copy link
Member

JounQin commented Jun 29, 2021

For both amd and esm version, there is a simpler and constant way.

import { Component, OnInit } from '@angular/core'
import { MonacoProviderService } from 'ng-monaco-editor'

@Component({
  selector: 'app-root',
  styleUrls: ['./app.component.css'],
  template: `
    <ng-monaco-editor
      style="height: 300px"
      [modelUri]="modelUri"
      [(ngModel)]="jsonCode"
      [options]="options"
    ></ng-monaco-editor>
  `,
})
export class AppComponent implements OnInit {
  jsonCode = ['{', '    "p1": "v3",', '    "p2": false', '}'].join('\n')

  options = {
    language: 'json',
  }

  modelUri = 'a://b/foo.json'

  constructor(private readonly monacoProvider: MonacoProviderService) {}

  async ngOnInit() {
    const monaco = await this.monacoProvider.initMonaco()
    monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
      validate: true,
      schemas: [
        {
          uri: 'http://myserver/foo-schema.json',
          fileMatch: [this.modelUri],
          schema: {
            type: 'object',
            properties: {
              p1: {
                enum: ['v1', 'v2'],
              },
              p2: {
                enum: ['x1', 'x2'],
              },
            },
          },
        },
      ],
    })
  }
}

@JounQin
Copy link
Member

JounQin commented Jun 29, 2021

Maybe it's worth to notice this usage into README.

@bsivanov
Copy link
Contributor Author

That's exactly what I was looking for. I will try to create a PR in the readme as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants