text: 'use strict';
  
  Object.defineProperty(exports, '__esModule', { value: true });
  
  class JsonError extends Error {
      constructor(message, innerError) {
          super(innerError
              ? `${message}. See the inner exception for more details.`
              : message);
          this.message = message;
          this.innerError = innerError;
      }
  }
  
  class TransformError extends JsonError {
      constructor(schema, message, innerError) {
          super(message, innerError);
          this.schema = schema;
      }
  }
  
  class ParserError extends TransformError {
      constructor(schema, message, innerError) {
          super(schema, `JSON doesn't match to '${schema.target.name}' schema. ${message}`, innerError);
      }
  }
  
  class ValidationError extends JsonError {
  }
  
  class SerializerError extends JsonError {
      constructor(schemaName, message, innerError) {
          super(`Cannot serialize by '${schemaName}' schema. ${message}`, innerError);
          this.schemaName = schemaName;
      }
  }
  
  class KeyError extends ParserError {
      constructor(schema, keys, errors = {}) {
          super(schema, "Some keys doesn't match to schema");
          this.keys = keys;
          this.errors = errors;
      }
  }
  
  (function (JsonPropTypes) {
      JsonPropTypes[JsonPropTypes["Any"] = 0] = "Any";
      JsonPropTypes[JsonPropTypes["Boolean"] = 1] = "Boolean";
      JsonPropTypes[JsonPropTypes["Number"] = 2] = "Number";
      JsonPropTypes[JsonPropTypes["String"] = 3] = "String";
  })(exports.JsonPropTypes || (exports.JsonPropTypes = {}));
  
  function checkType(value, type) {
      switch (type) {
          case exports.JsonPropTypes.Boolean:
              return typeof value === "boolean";
          case exports.JsonPropTypes.Number:
              return typeof value === "number";
          case exports.JsonPropTypes.String:
              return typeof value === "string";
      }
      return true;
  }
  function throwIfTypeIsWrong(value, type) {
      if (!checkType(value, type)) {
          throw new TypeError(`Value must be ${exports.JsonPropTypes[type]}`);
      }
  }
  function isConvertible(target) {
      if (target && target.prototype) {
          if (target.prototype.toJSON && target.prototype.fromJSON) {
              return true;
          }
          else {
              return isConvertible(target.prototype);
          }
      }
      else {
          return !!(target && target.toJSON && target.fromJSON);
      }
  }
  
  class JsonSchemaStorage {
      constructor() {
          this.items = new Map();
      }
      has(target) {
          return this.items.has(target) || !!this.findParentSchema(target);
      }
      get(target) {
          const schema = this.items.get(target) || this.findParentSchema(target);
          if (!schema) {
              throw new Error("Cannot get schema for current target");
          }
          return schema;
      }
      create(target) {
          const schema = { names: {} };
          const parentSchema = this.findParentSchema(target);
          if (parentSchema) {
              Object.assign(schema, parentSchema);
              schema.names = {};
              for (const name in parentSchema.names) {
                  schema.names[name] = Object.assign({}, parentSchema.names[name]);
              }
          }
          schema.target = target;
          return schema;
      }
      set(target, schema) {
          this.items.set(target, schema);
          return this;
      }
      findParentSchema(target) {
          const parent = target.__proto__;
          if (parent) {
              const schema = this.items.get(parent);
              return schema || this.findParentSchema(parent);
          }
          return null;
      }
  }
  
  const DEFAULT_SCHEMA = "default";
  const schemaStorage = new JsonSchemaStorage();
  
  class PatternValidation {
      constructor(pattern) {
          this.pattern = new RegExp(pattern);
      }
      validate(value) {
          const pattern = new RegExp(this.pattern.source, this.pattern.flags);
          if (typeof value !== "string") {
              throw new ValidationError("Incoming value must be string");
          }
          if (!pattern.exec(value)) {
              throw new ValidationError(`Value doesn't match to pattern '${pattern.toString()}'`);
          }
      }
  }
  
  class InclusiveValidation {
      constructor(min = Number.MIN_VALUE, max = Number.MAX_VALUE) {
          this.min = min;
          this.max = max;
      }
      validate(value) {
          throwIfTypeIsWrong(value, exports.JsonPropTypes.Number);
          if (!(this.min <= value && value <= this.max)) {
              const min = this.min === Number.MIN_VALUE ? "MIN" : this.min;
              const max = this.max === Number.MAX_VALUE ? "MAX" : this.max;
              throw new ValidationError(`Value doesn't match to diapason [${min},${max}]`);
          }
      }
  }
  
  class ExclusiveValidation {
      constructor(min = Number.MIN_VALUE, max = Number.MAX_VALUE) {
          this.min = min;
          this.max = max;
      }
      validate(value) {
          throwIfTypeIsWrong(value, exports.JsonPropTypes.Number);
          if (!(this.min < value && value < this.max)) {
              const min = this.min === Number.MIN_VALUE ? "MIN" : this.min;
              const max = this.max === Number.MAX_VALUE ? "MAX" : this.max;
              throw new ValidationError(`Value doesn't match to diapason (${min},${max})`);
          }
      }
  }
  
  class LengthValidation {
      constructor(length, minLength, maxLength) {
          this.length = length;
          this.minLength = minLength;
          this.maxLength = maxLength;
      }
      validate(value) {
          if (this.length !== undefined) {
              if (value.length !== this.length) {
                  throw new ValidationError(`Value length must be exactly ${this.length}.`);
              }
              return;
          }
          if (this.minLength !== undefined) {
              if (value.length < this.minLength) {
                  throw new ValidationError(`Value length must be more than ${this.minLength}.`);
              }
          }
          if (this.maxLength !== undefined) {
              if (value.length > this.maxLength) {
                  throw new ValidationError(`Value length must be less than ${this.maxLength}.`);
              }
          }
      }
  }
  
  class EnumerationValidation {
      constructor(enumeration) {
          this.enumeration = enumeration;
      }
      validate(value) {
          throwIfTypeIsWrong(value, exports.JsonPropTypes.String);
          if (!this.enumeration.includes(value)) {
              throw new ValidationError(`Value must be one of ${this.enumeration.map((v) => `'${v}'`).join(", ")}`);
          }
      }
  }
  
  class JsonTransform {
      static checkValues(data, schemaItem) {
          const values = Array.isArray(data) ? data : [data];
          for (const value of values) {
              for (const validation of schemaItem.validations) {
                  if (validation instanceof LengthValidation && schemaItem.repeated) {
                      validation.validate(data);
                  }
                  else {
                      validation.validate(value);
                  }
              }
          }
      }
      static checkTypes(value, schemaItem) {
          if (schemaItem.repeated && !Array.isArray(value)) {
              throw new TypeError("Value must be Array");
          }
          if (typeof schemaItem.type === "number") {
              const values = Array.isArray(value) ? value : [value];
              for (const v of values) {
                  throwIfTypeIsWrong(v, schemaItem.type);
              }
          }
      }
      static getSchemaByName(schema, name = DEFAULT_SCHEMA) {
          return { ...schema.names[DEFAULT_SCHEMA], ...schema.names[name] };
      }
  }
  
  class JsonSerializer extends JsonTransform {
      static serialize(obj, options, replacer, space) {
          const json = this.toJSON(obj, options);
          return JSON.stringify(json, replacer, space);
      }
      static toJSON(obj, options = {}) {
          let res;
          let targetSchema = options.targetSchema;
          const schemaName = options.schemaName || DEFAULT_SCHEMA;
          if (isConvertible(obj)) {
              return obj.toJSON();
          }
          if (Array.isArray(obj)) {
              res = [];
              for (const item of obj) {
                  res.push(this.toJSON(item, options));
              }
          }
          else if (typeof obj === "object") {
              if (targetSchema && !schemaStorage.has(targetSchema)) {
                  throw new JsonError("Cannot get schema for `targetSchema` param");
              }
              targetSchema = (targetSchema || obj.constructor);
              if (schemaStorage.has(targetSchema)) {
                  const schema = schemaStorage.get(targetSchema);
                  res = {};
                  const namedSchema = this.getSchemaByName(schema, schemaName);
                  for (const key in namedSchema) {
                      try {
                          const item = namedSchema[key];
                          const objItem = obj[key];
                          let value;
                          if ((item.optional && objItem === undefined)
                              || (item.defaultValue !== undefined && objItem === item.defaultValue)) {
                              continue;
                          }
                          if (!item.optional && objItem === undefined) {
                              throw new SerializerError(targetSchema.name, `Property '${key}' is required.`);
                          }
                          if (typeof item.type === "number") {
                              if (item.converter) {
                                  if (item.repeated) {
                                      value = objItem.map((el) => item.converter.toJSON(el, obj));
                                  }
                                  else {
                                      value = item.converter.toJSON(objItem, obj);
                                  }
                              }
                              else {
                                  value = objItem;
                              }
                          }
                          else {
                              if (item.repeated) {
                                  value = objItem.map((el) => this.toJSON(el, { schemaName }));
                              }
                              else {
                                  value = this.toJSON(objItem, { schemaName });
                              }
                          }
                          this.checkTypes(value, item);
                          this.checkValues(value, item);
                          res[item.name || key] = value;
                      }
                      catch (e) {
                          if (e instanceof SerializerError) {
                              throw e;
                          }
                          else {
                              throw new SerializerError(schema.target.name, `Property '${key}' is wrong. ${e.message}`, e);
                          }
                      }
                  }
              }
              else {
                  res = {};
                  for (const key in obj) {
                      res[key] = this.toJSON(obj[key], { schemaName });
                  }
              }
          }
          else {
              res = obj;
          }
          return res;
      }
  }
  
  class JsonParser extends JsonTransform {
      static parse(data, options) {
          const obj = JSON.parse(data);
          return this.fromJSON(obj, options);
      }
      static fromJSON(target, options) {
          const targetSchema = options.targetSchema;
          const schemaName = options.schemaName || DEFAULT_SCHEMA;
          const obj = new targetSchema();
          if (isConvertible(obj)) {
              return obj.fromJSON(target);
          }
          const schema = schemaStorage.get(targetSchema);
          const namedSchema = this.getSchemaByName(schema, schemaName);
          const keyErrors = {};
          if (options.strictProperty && !Array.isArray(target)) {
              JsonParser.checkStrictProperty(target, namedSchema, schema);
          }
          for (const key in namedSchema) {
              try {
                  const item = namedSchema[key];
                  const name = item.name || key;
                  const value = target[name];
                  if (value === undefined && (item.optional || item.defaultValue !== undefined)) {
                      continue;
                  }
                  if (!item.optional && value === undefined) {
                      throw new ParserError(schema, `Property '${name}' is required.`);
                  }
                  this.checkTypes(value, item);
                  this.checkValues(value, item);
                  if (typeof (item.type) === "number") {
                      if (item.converter) {
                          if (item.repeated) {
                              obj[key] = value.map((el) => item.converter.fromJSON(el, obj));
                          }
                          else {
                              obj[key] = item.converter.fromJSON(value, obj);
                          }
                      }
                      else {
                          obj[key] = value;
                      }
                  }
                  else {
                      const newOptions = {
                          ...options,
                          targetSchema: item.type,
                          schemaName,
                      };
                      if (item.repeated) {
                          obj[key] = value.map((el) => this.fromJSON(el, newOptions));
                      }
                      else {
                          obj[key] = this.fromJSON(value, newOptions);
                      }
                  }
              }
              catch (e) {
                  if (!(e instanceof ParserError)) {
                      e = new ParserError(schema, `Property '${key}' is wrong. ${e.message}`, e);
                  }
                  if (options.strictAllKeys) {
                      keyErrors[key] = e;
                  }
                  else {
                      throw e;
                  }
              }
          }
          const keys = Object.keys(keyErrors);
          if (keys.length) {
              throw new KeyError(schema, keys, keyErrors);
          }
          return obj;
      }
      static checkStrictProperty(target, namedSchema, schema) {
          const jsonProps = Object.keys(target);
          const schemaProps = Object.keys(namedSchema);
          const keys = [];
          for (const key of jsonProps) {
              if (schemaProps.indexOf(key) === -1) {
                  keys.push(key);
              }
          }
          if (keys.length) {
              throw new KeyError(schema, keys);
          }
      }
  }
  
  function getValidations(item) {
      const validations = [];
      if (item.pattern) {
          validations.push(new PatternValidation(item.pattern));
      }
      if (item.type === exports.JsonPropTypes.Number || item.type === exports.JsonPropTypes.Any) {
          if (item.minInclusive !== undefined || item.maxInclusive !== undefined) {
              validations.push(new InclusiveValidation(item.minInclusive, item.maxInclusive));
          }
          if (item.minExclusive !== undefined || item.maxExclusive !== undefined) {
              validations.push(new ExclusiveValidation(item.minExclusive, item.maxExclusive));
          }
          if (item.enumeration !== undefined) {
              validations.push(new EnumerationValidation(item.enumeration));
          }
      }
      if (item.type === exports.JsonPropTypes.String || item.repeated || item.type === exports.JsonPropTypes.Any) {
          if (item.length !== undefined || item.minLength !== undefined || item.maxLength !== undefined) {
              validations.push(new LengthValidation(item.length, item.minLength, item.maxLength));
          }
      }
      return validations;
  }
  const JsonProp = (options = {}) => (target, propertyKey) => {
      const errorMessage = `Cannot set type for ${propertyKey} property of ${target.constructor.name} schema`;
      let schema;
      if (!schemaStorage.has(target.constructor)) {
          schema = schemaStorage.create(target.constructor);
          schemaStorage.set(target.constructor, schema);
      }
      else {
          schema = schemaStorage.get(target.constructor);
          if (schema.target !== target.constructor) {
              schema = schemaStorage.create(target.constructor);
              schemaStorage.set(target.constructor, schema);
          }
      }
      const defaultSchema = {
          type: exports.JsonPropTypes.Any,
          validations: [],
      };
      const copyOptions = Object.assign(defaultSchema, options);
      copyOptions.validations = getValidations(copyOptions);
      if (typeof copyOptions.type !== "number") {
          if (!schemaStorage.has(copyOptions.type) && !isConvertible(copyOptions.type)) {
              throw new Error(`${errorMessage}. Assigning type doesn't have schema.`);
          }
      }
      let schemaNames;
      if (Array.isArray(options.schema)) {
          schemaNames = options.schema;
      }
      else {
          schemaNames = [options.schema || DEFAULT_SCHEMA];
      }
      for (const schemaName of schemaNames) {
          if (!schema.names[schemaName]) {
              schema.names[schemaName] = {};
          }
          const namedSchema = schema.names[schemaName];
          namedSchema[propertyKey] = copyOptions;
      }
  };
  
  exports.JsonError = JsonError;
  exports.JsonParser = JsonParser;
  exports.JsonProp = JsonProp;
  exports.JsonSerializer = JsonSerializer;
  exports.KeyError = KeyError;
  exports.ParserError = ParserError;
  exports.SerializerError = SerializerError;
  exports.TransformError = TransformError;
  exports.ValidationError = ValidationError;
  