Repository: ajv-validator/ajv Branch: master Commit: 142ce84b807c Files: 433 Total size: 1.8 MB Directory structure: gitextract_hfo2xbb4/ ├── .eslintrc.js ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-or-error-report.md │ │ ├── change.md │ │ ├── compatibility.md │ │ ├── installation.md │ │ └── typescript.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── config.yml │ ├── dependabot.yml │ └── workflows/ │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .npmrc ├── .prettierignore ├── .runkit_example.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchmark/ │ ├── jtd.js │ └── package.json ├── bower.json ├── docs/ │ ├── .vuepress/ │ │ ├── components/ │ │ │ ├── Button.vue │ │ │ ├── Column.vue │ │ │ ├── Columns.vue │ │ │ ├── Contributors.vue │ │ │ ├── Feature.vue │ │ │ ├── Features.vue │ │ │ ├── FooterColumn.vue │ │ │ ├── FooterColumns.vue │ │ │ ├── GitHub.vue │ │ │ ├── HeroSection.vue │ │ │ ├── HomePage.vue │ │ │ ├── HomeSection.vue │ │ │ ├── NewsHome.vue │ │ │ ├── NewsIndex.vue │ │ │ ├── NewsPost.vue │ │ │ ├── NewsPostMeta.vue │ │ │ ├── Projects.vue │ │ │ ├── Sponsors.vue │ │ │ ├── Subscribe.vue │ │ │ ├── Testimonial.vue │ │ │ └── Testimonials.vue │ │ ├── config.js │ │ ├── styles/ │ │ │ ├── index.styl │ │ │ └── palette.styl │ │ └── theme/ │ │ ├── LICENSE │ │ ├── components/ │ │ │ ├── AlgoliaSearchBox.vue │ │ │ ├── DropdownLink.vue │ │ │ ├── DropdownTransition.vue │ │ │ ├── Home.vue │ │ │ ├── NavLink.vue │ │ │ ├── NavLinks.vue │ │ │ ├── Navbar.vue │ │ │ ├── Page.vue │ │ │ ├── PageEdit.vue │ │ │ ├── PageNav.vue │ │ │ ├── Sidebar.vue │ │ │ ├── SidebarButton.vue │ │ │ ├── SidebarGroup.vue │ │ │ ├── SidebarLink.vue │ │ │ └── SidebarLinks.vue │ │ ├── global-components/ │ │ │ ├── Badge.vue │ │ │ ├── CodeBlock.vue │ │ │ └── CodeGroup.vue │ │ ├── index.js │ │ ├── layouts/ │ │ │ ├── 404.vue │ │ │ └── Layout.vue │ │ ├── noopModule.js │ │ ├── styles/ │ │ │ ├── arrow.styl │ │ │ ├── code.styl │ │ │ ├── config.styl │ │ │ ├── custom-blocks.styl │ │ │ ├── index.styl │ │ │ ├── mobile.styl │ │ │ ├── toc.styl │ │ │ └── wrapper.styl │ │ └── util/ │ │ └── index.js │ ├── README.md │ ├── api.md │ ├── codegen.md │ ├── coercion.md │ ├── components.md │ ├── faq.md │ ├── guide/ │ │ ├── async-validation.md │ │ ├── combining-schemas.md │ │ ├── environments.md │ │ ├── formats.md │ │ ├── getting-started.md │ │ ├── managing-schemas.md │ │ ├── modifying-data.md │ │ ├── schema-language.md │ │ ├── typescript.md │ │ ├── user-keywords.md │ │ └── why-ajv.md │ ├── json-schema.md │ ├── json-type-definition.md │ ├── keywords.md │ ├── news/ │ │ ├── 2020-08-14-mozilla-grant-openjs-foundation.md │ │ ├── 2020-12-15-ajv-version-7-released.md │ │ ├── 2021-03-07-ajv-supports-json-type-definition.md │ │ ├── 2021-03-27-ajv-version-8-released.md │ │ ├── 2021-04-24-ajv-online-event.md │ │ ├── 2021-05-24-ajv-online-event-video.md │ │ ├── 2021-07-22-ajv-microsoft-foss-fund-award.md │ │ └── README.md │ ├── options.md │ ├── packages/ │ │ └── README.md │ ├── security.md │ ├── standalone.md │ ├── strict-mode.md │ ├── testimonials.md │ └── v6-to-v8-migration.md ├── karma.conf.js ├── lib/ │ ├── 2019.ts │ ├── 2020.ts │ ├── ajv.ts │ ├── compile/ │ │ ├── codegen/ │ │ │ ├── code.ts │ │ │ ├── index.ts │ │ │ └── scope.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── jtd/ │ │ │ ├── parse.ts │ │ │ ├── serialize.ts │ │ │ └── types.ts │ │ ├── names.ts │ │ ├── ref_error.ts │ │ ├── resolve.ts │ │ ├── rules.ts │ │ ├── util.ts │ │ └── validate/ │ │ ├── applicability.ts │ │ ├── boolSchema.ts │ │ ├── dataType.ts │ │ ├── defaults.ts │ │ ├── index.ts │ │ ├── keyword.ts │ │ └── subschema.ts │ ├── core.ts │ ├── jtd.ts │ ├── refs/ │ │ ├── data.json │ │ ├── json-schema-2019-09/ │ │ │ ├── index.ts │ │ │ ├── meta/ │ │ │ │ ├── applicator.json │ │ │ │ ├── content.json │ │ │ │ ├── core.json │ │ │ │ ├── format.json │ │ │ │ ├── meta-data.json │ │ │ │ └── validation.json │ │ │ └── schema.json │ │ ├── json-schema-2020-12/ │ │ │ ├── index.ts │ │ │ ├── meta/ │ │ │ │ ├── applicator.json │ │ │ │ ├── content.json │ │ │ │ ├── core.json │ │ │ │ ├── format-annotation.json │ │ │ │ ├── meta-data.json │ │ │ │ ├── unevaluated.json │ │ │ │ └── validation.json │ │ │ └── schema.json │ │ ├── json-schema-draft-06.json │ │ ├── json-schema-draft-07.json │ │ ├── json-schema-secure.json │ │ └── jtd-schema.ts │ ├── runtime/ │ │ ├── equal.ts │ │ ├── parseJson.ts │ │ ├── quote.ts │ │ ├── re2.ts │ │ ├── timestamp.ts │ │ ├── ucs2length.ts │ │ ├── uri.ts │ │ └── validation_error.ts │ ├── standalone/ │ │ ├── index.ts │ │ └── instance.ts │ ├── types/ │ │ ├── index.ts │ │ ├── json-schema.ts │ │ └── jtd-schema.ts │ └── vocabularies/ │ ├── applicator/ │ │ ├── additionalItems.ts │ │ ├── additionalProperties.ts │ │ ├── allOf.ts │ │ ├── anyOf.ts │ │ ├── contains.ts │ │ ├── dependencies.ts │ │ ├── dependentSchemas.ts │ │ ├── if.ts │ │ ├── index.ts │ │ ├── items.ts │ │ ├── items2020.ts │ │ ├── not.ts │ │ ├── oneOf.ts │ │ ├── patternProperties.ts │ │ ├── prefixItems.ts │ │ ├── properties.ts │ │ ├── propertyNames.ts │ │ └── thenElse.ts │ ├── code.ts │ ├── core/ │ │ ├── id.ts │ │ ├── index.ts │ │ └── ref.ts │ ├── discriminator/ │ │ ├── index.ts │ │ └── types.ts │ ├── draft2020.ts │ ├── draft7.ts │ ├── dynamic/ │ │ ├── dynamicAnchor.ts │ │ ├── dynamicRef.ts │ │ ├── index.ts │ │ ├── recursiveAnchor.ts │ │ └── recursiveRef.ts │ ├── errors.ts │ ├── format/ │ │ ├── format.ts │ │ └── index.ts │ ├── jtd/ │ │ ├── discriminator.ts │ │ ├── elements.ts │ │ ├── enum.ts │ │ ├── error.ts │ │ ├── index.ts │ │ ├── metadata.ts │ │ ├── nullable.ts │ │ ├── optionalProperties.ts │ │ ├── properties.ts │ │ ├── ref.ts │ │ ├── type.ts │ │ ├── union.ts │ │ └── values.ts │ ├── metadata.ts │ ├── next.ts │ ├── unevaluated/ │ │ ├── index.ts │ │ ├── unevaluatedItems.ts │ │ └── unevaluatedProperties.ts │ └── validation/ │ ├── const.ts │ ├── dependentRequired.ts │ ├── enum.ts │ ├── index.ts │ ├── limitContains.ts │ ├── limitItems.ts │ ├── limitLength.ts │ ├── limitNumber.ts │ ├── limitProperties.ts │ ├── multipleOf.ts │ ├── pattern.ts │ ├── required.ts │ └── uniqueItems.ts ├── package.json ├── rollup.config.js ├── scripts/ │ ├── .eslintrc.yml │ ├── bundle.js │ ├── get-ajv-packages │ ├── get-contributors.js │ ├── jsontests.js │ ├── prepare-site │ ├── prepare-tests │ ├── publish-bundles │ └── publish-site ├── spec/ │ ├── .eslintrc.yml │ ├── _json/ │ │ └── README.md │ ├── after_test.ts │ ├── ajv.spec.ts │ ├── ajv.ts │ ├── ajv2019.ts │ ├── ajv2020.ts │ ├── ajv_all_instances.ts │ ├── ajv_async_instances.ts │ ├── ajv_instances.ts │ ├── ajv_jtd.ts │ ├── ajv_options.ts │ ├── ajv_standalone.ts │ ├── async/ │ │ ├── boolean.json │ │ ├── compound.json │ │ ├── format.json │ │ ├── items.json │ │ ├── keyword.json │ │ ├── no_async.json │ │ └── properties.json │ ├── async.spec.ts │ ├── async_schemas.spec.ts │ ├── async_validate.spec.ts │ ├── boolean.spec.ts │ ├── chai.ts │ ├── chai_type.ts │ ├── codegen.spec.ts │ ├── coercion.spec.ts │ ├── discriminator.spec.ts │ ├── dynamic-ref.spec.ts │ ├── errors.spec.ts │ ├── extras/ │ │ ├── $data/ │ │ │ ├── absolute_ref.json │ │ │ ├── const.json │ │ │ ├── enum.json │ │ │ ├── exclusiveMaximum.json │ │ │ ├── exclusiveMinimum.json │ │ │ ├── format.json │ │ │ ├── maxItems.json │ │ │ ├── maxLength.json │ │ │ ├── maxProperties.json │ │ │ ├── maximum.json │ │ │ ├── minItems.json │ │ │ ├── minLength.json │ │ │ ├── minProperties.json │ │ │ ├── minimum.json │ │ │ ├── multipleOf.json │ │ │ ├── pattern.json │ │ │ ├── required.json │ │ │ └── uniqueItems.json │ │ ├── const.json │ │ ├── contains.json │ │ ├── exclusiveMaximum.json │ │ └── exclusiveMinimum.json │ ├── extras.spec.ts │ ├── issues/ │ │ ├── 1001_addKeyword_and_schema_without_id.spec.ts │ │ ├── 1344_non_root_recursive_ref_standalone.spec.ts │ │ ├── 1414_base_uri_change.spec.ts │ │ ├── 1501_jtd_many_properties.spec.ts │ │ ├── 1515_evaluated_properties_nested_anyof.spec.ts │ │ ├── 1539_add_keyword_name_to_validation_error.spec.ts │ │ ├── 1625_evaluated_truthy_pattern_properties.spec.ts │ │ ├── 1683_re2_engine.spec.ts │ │ ├── 1819_mincontains.spec.ts │ │ ├── 181_allErrors_custom_keyword_skipped.spec.ts │ │ ├── 182_nan_validation.spec.ts │ │ ├── 1935_integer_narrowing_subschema.spec.ts │ │ ├── 1949_jtd_empty_values.spec.ts │ │ ├── 1971_jtd_discriminator.spec.ts │ │ ├── 2001_jtd_only_optional_properties.spec.ts │ │ ├── 204_options_schemas_data_together.spec.ts │ │ ├── 210_mutual_recur_frags.spec.ts │ │ ├── 240_mutual_recur_frags_common_ref.spec.ts │ │ ├── 259_validate_meta_against_itself.spec.ts │ │ ├── 273_error_schemaPath_refd_schema.spec.ts │ │ ├── 342_uniqueItems_non-json_objects.spec.ts │ │ ├── 485_type_validation_priority.spec.ts │ │ ├── 50_refs_with_definitions.spec.ts │ │ ├── 521_wrong_warning_id_property.spec.ts │ │ ├── 743_removeAdditional_to_remove_proto.spec.ts │ │ ├── 768_passContext_recursive_ref.spec.ts │ │ ├── 815_id_updates_ref_base.spec.ts │ │ ├── 8_shared_refs.spec.ts │ │ ├── 955_removeAdditional_custom_keywords.spec.ts │ │ ├── cve_2025_69873_redos_attack.spec.ts │ │ └── re2.ts │ ├── javacript.spec.js │ ├── json-schema.spec.ts │ ├── json_parse_tests.json │ ├── jtd-schema.spec.ts │ ├── jtd-timestamps.spec.ts │ ├── keyword.spec.ts │ ├── options/ │ │ ├── comment.spec.ts │ │ ├── int32range.spec.ts │ │ ├── meta_validateSchema.spec.ts │ │ ├── nullable.spec.ts │ │ ├── options_add_schemas.spec.ts │ │ ├── options_code.spec.ts │ │ ├── options_refs.spec.ts │ │ ├── options_reporting.spec.ts │ │ ├── options_validation.spec.ts │ │ ├── ownProperties.spec.ts │ │ ├── removeAdditional.spec.ts │ │ ├── schemaId.spec.ts │ │ ├── strict.spec.ts │ │ ├── strictDefaults.spec.ts │ │ ├── strictKeywords.spec.ts │ │ ├── strictNumbers.spec.ts │ │ ├── unicodeRegExp.spec.ts │ │ ├── unknownFormats.spec.ts │ │ └── useDefaults.spec.ts │ ├── remotes/ │ │ ├── bar.json │ │ ├── buu.json │ │ ├── first.json │ │ ├── foo.json │ │ ├── hyper-schema.json │ │ ├── name.json │ │ ├── node.json │ │ ├── scope_change.json │ │ ├── second.json │ │ └── tree.json │ ├── resolve.spec.ts │ ├── schema-tests.spec.ts │ ├── security/ │ │ ├── array.json │ │ ├── object.json │ │ └── string.json │ ├── security.spec.ts │ ├── standalone.spec.ts │ ├── tests/ │ │ ├── issues/ │ │ │ ├── 12_restoring_root_after_resolve.json │ │ │ ├── 13_root_ref_in_ref_in_remote_ref.json │ │ │ ├── 14_ref_in_remote_ref_with_id.json │ │ │ ├── 1668_not_with_other_keywords.json │ │ │ ├── 170_ref_and_id_in_sibling.json │ │ │ ├── 17_escaping_pattern_property.json │ │ │ ├── 19_required_many_properties.json │ │ │ ├── 1_ids_in_refs.json │ │ │ ├── 20_failing_to_parse_schema.json │ │ │ ├── 226_json_with_control_chars.json │ │ │ ├── 27_1_recursive_raml_schema.json │ │ │ ├── 27_recursive_reference.json │ │ │ ├── 28_escaping_pattern_error.json │ │ │ ├── 2_root_ref_in_ref.json │ │ │ ├── 311_quotes_in_refs.json │ │ │ ├── 33_json_schema_latest.json │ │ │ ├── 413_dependencies_with_quote.json │ │ │ ├── 490_integer_validation.json │ │ │ ├── 502_contains_empty_array_with_ref_in_another_property.json │ │ │ ├── 5_adding_dependency_after.json │ │ │ ├── 5_recursive_references.json │ │ │ ├── 62_resolution_scope_change.json │ │ │ ├── 63_id_property_not_in_schema.json │ │ │ ├── 70_1_recursive_hash_ref_in_remote_ref.json │ │ │ ├── 70_swagger_schema.json │ │ │ ├── 861_empty_propertynames.json │ │ │ ├── 87_$_property.json │ │ │ └── 94_dependencies_fail.json │ │ ├── rules/ │ │ │ ├── allOf.json │ │ │ ├── anyOf.json │ │ │ ├── comment.json │ │ │ ├── dependencies.json │ │ │ ├── format.json │ │ │ ├── if.json │ │ │ ├── items.json │ │ │ ├── oneOf.json │ │ │ ├── required.json │ │ │ ├── type.json │ │ │ └── uniqueItems.json │ │ └── schemas/ │ │ ├── advanced.json │ │ ├── basic.json │ │ ├── complex.json │ │ ├── complex2.json │ │ ├── complex3.json │ │ ├── cosmicrealms.json │ │ └── medium.json │ ├── tsconfig.json │ └── types/ │ ├── async-validate.spec.ts │ ├── error-parameters.spec.ts │ ├── json-schema.spec.ts │ └── jtd-schema.spec.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.js ================================================ const jsConfig = require("@ajv-validator/config/.eslintrc_js") const tsConfig = require("@ajv-validator/config/.eslintrc") module.exports = { env: { es6: true, node: true, }, overrides: [ jsConfig, { ...tsConfig, files: ["*.ts"], rules: { ...tsConfig.rules, complexity: ["error", 17], "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-implied-eval": "off", "@typescript-eslint/no-invalid-this": "off", "@typescript-eslint/no-parameter-properties": "off", "@typescript-eslint/no-unnecessary-condition": "warn", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/restrict-template-expressions": "off", }, }, ], } ================================================ FILE: .github/CODEOWNERS ================================================ @epoberezkin ================================================ FILE: .github/FUNDING.yml ================================================ github: epoberezkin tidelift: "npm/ajv" open_collective: "ajv" ================================================ FILE: .github/ISSUE_TEMPLATE/bug-or-error-report.md ================================================ --- name: Bug or error report about: Please use for issues related to incorrect validation behaviour title: "" labels: "bug report" assignees: "" --- **What version of Ajv are you using? Does the issue happen if you use the latest version?** **Ajv options object** ```javascript ``` **JSON Schema** ```json ``` **Sample data** ```json ``` **Your code** ```javascript ``` **Validation result, data AFTER validation, error messages** ``` ``` **What results did you expect?** **Are you going to resolve the issue?** ================================================ FILE: .github/ISSUE_TEMPLATE/change.md ================================================ --- name: Feature or change proposal about: For proposals of new features, options or some other improvements title: "" labels: "enhancement" assignees: "" --- **What version of Ajv you are you using?** **What problem do you want to solve?** **What do you think is the correct solution to problem?** **Will you be able to implement it?** ================================================ FILE: .github/ISSUE_TEMPLATE/compatibility.md ================================================ --- name: Browser and compatibility issue about: For issues that only happen in a specific environment title: "" labels: "compatibility" assignees: "" --- **The version of Ajv you are using** **The environment you have the problem with** **Your code (please make it as small as possible to reproduce the issue)** **If your issue is in the browser, please list the other packages loaded in the page in the order they are loaded. Please check if the issue gets resolved (or results change) if you move Ajv bundle closer to the top** **Results in node.js v8+** **Results and error messages in your platform** ================================================ FILE: .github/ISSUE_TEMPLATE/installation.md ================================================ --- name: Installation and dependency issue about: For issues that happen during installation title: "" labels: "installation" assignees: "" --- **The version of Ajv you are using** **Operating system and node.js version** **Package manager and its version** **Link to (or contents of) package.json** **Error messages** **The output of `npm ls`** ================================================ FILE: .github/ISSUE_TEMPLATE/typescript.md ================================================ --- name: Missing or incorrect type definition about: Please use for issues related to typescript types title: "" labels: "typescript" assignees: "" --- **What version of Ajv are you using? Does the issue happen if you use the latest version?** **Your typescript code** ```typescript ``` **Typescript compiler error messages** ``` ``` **Describe the change that should be made to address the issue?** **Are you going to resolve the issue?** ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ **What version of Ajv are you using? Does the issue happen if you use the latest version?** **Ajv options object** ```javascript ``` **JSON Schema** ```json ``` **Sample data** ```json ``` **Your code** ```javascript ``` **Validation result, data AFTER validation, error messages** ``` ``` **What results did you expect?** **Are you going to resolve the issue?** ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ **What issue does this pull request resolve?** **What changes did you make?** **Is there anything that requires more attention while reviewing?** ================================================ FILE: .github/config.yml ================================================ # Please supply comments to be used for GitHub labels githubLabels: bug: > Bug confirmed - to be fixed. PR is welcome! # duplicate: > # enhancement: > # good first issue: > # help wanted: > # invalid: > # question: > # wont fix: > bug report: > Thank you for the report! If you didn't post a code sample to RunKit yet, please clone this notebook https://runkit.com/esp/ajv-issue, post the code sample that demonstrates the bug and post the link here. It will speed up the investigation and fixing! json schema: > This question is about the usage of JSON Schema specification - it is not specific to Ajv. Please use JSON Schema reference materials or [submit the question to Stack Overflow](https://stackoverflow.com/questions/ask?tags=jsonschema,ajv). - [JSON Schema specification](http://json-schema.org/) - [Tutorial by Space Telescope Science Institute](http://json-schema.org/understanding-json-schema/) - [validation keywords](https://github.com/ajv-validator/ajv#validation-keywords) (in Ajv docs) - [combining schemas](https://github.com/ajv-validator/ajv#ref) (in Ajv docs) - [Tutorial by @epoberezkin](https://code.tutsplus.com/tutorials/validating-data-with-json-schema-part-1--cms-25343) ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: npm directory: "/" schedule: interval: daily open-pull-requests-limit: 10 ignore: - dependency-name: "@types/node" versions: - 15.0.0 - dependency-name: eslint-config-prettier versions: - 8.0.0 - 8.1.0 - 8.2.0 - dependency-name: karma versions: - 6.0.3 - 6.0.4 - 6.1.0 - 6.1.1 - 6.1.2 - 6.2.0 - 6.3.0 - 6.3.1 ================================================ FILE: .github/workflows/build.yml ================================================ name: build on: push: branches: [master] pull_request: branches: ["*"] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x, 20.x, 21.x] steps: - uses: actions/checkout@v4 - name: use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install - run: git submodule update --init - name: update website if: ${{ github.event_name == 'push' && matrix.node-version == '18.x' }} run: ./scripts/publish-site env: GH_TOKEN_PUBLIC: ${{ secrets.GH_TOKEN_PUBLIC }} GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} - run: npm run build - run: npm run test-ci - name: coveralls uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/publish.yml ================================================ name: publish on: release: types: [published] jobs: publish-npm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 18 registry-url: https://registry.npmjs.org/ - run: npm install - run: git submodule update --init - run: npm run test-ci - name: Publish beta version to npm if: ${{ github.event.release.prerelease }} run: npm publish --tag beta env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish to npm if: ${{ !github.event.release.prerelease }} run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Commit bundles to ajv-dist run: ./scripts/publish-bundles env: GH_TOKEN_PUBLIC: ${{ secrets.GH_TOKEN_PUBLIC }} GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} ================================================ FILE: .gitignore ================================================ # Logs logs *.log # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules .DS_Store # Browserified tests .browser # compiled typescript dist/ # browser bundles bundle/ package-lock.json spec/_json/*.js # docs docs/code_of_conduct.md docs/contributing.md docs/license.md docs/.vuepress/components/Contributors/ docs/packages/* !docs/packages/README.md ================================================ FILE: .gitmodules ================================================ [submodule "spec/JSON-Schema-Test-Suite"] path = spec/JSON-Schema-Test-Suite url = https://github.com/json-schema/JSON-Schema-Test-Suite.git [submodule "spec/json-typedef-spec"] path = spec/json-typedef-spec url = https://github.com/jsontypedef/json-typedef-spec.git ================================================ FILE: .npmrc ================================================ package-lock=false ================================================ FILE: .prettierignore ================================================ spec/JSON-Schema-Test-Suite spec/json-typedef-spec .browser coverage dist bundle .nyc_output spec/_json docs/.vuepress/components/_contributors.js ================================================ FILE: .runkit_example.js ================================================ const Ajv = require("ajv") const ajv = new Ajv({allErrors: true}) const schema = { type: "object", properties: { foo: {type: "string"}, bar: {type: "number", maximum: 3}, }, required: ["foo", "bar"], additionalProperties: false, } const validate = ajv.compile(schema) test({foo: "abc", bar: 2}) test({foo: 2, bar: 4}) function test(data) { const valid = validate(data) if (valid) console.log("Valid!") else console.log("Invalid: " + ajv.errorsText(validate.errors)) } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ --- permalink: /code_of_conduct --- # Contributor Covenant Code of Conduct ### Our Pledge We commit to creating and maintaining an open and welcoming environment. We, as contributors and maintainers, commit to building a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards **Behaviour that contributes to creating a positive environment include**: - Using welcoming and inclusive language - Consider when identity words like race or ethnicity matter - Be conscious of language with discriminatory connotations (e.g. gendered, ableist, racialized phrases) - Be open to being corrected if you make a mistake - it’s okay to mess up, what matters is your follow up - Gracefully accepting constructive criticism - Showing empathy towards other community members - Treat other community members and project team members with respect - Report if you witness harassment or wrongdoing in our spaces **Unacceptable behaviour by participants include**: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behaviour. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope The goal of this Code of Conduct is to set standards and expectations around how we interact within this community. It’s scope applies to all project participants and covers all interactions within the community associated with this project including, but not limited to, email communication, issue trackers, source code repositories, forums, and social media. Examples of representing a project or community include: - Using an official project e-mail address - Posting via an official social media account - Acting as an appointed representative at an online or offline event Representation of a project may be further defined and clarified by project maintainers. ### Enforcement We will not tolerate abuse, harassment, or any other unacceptable behaviour made against community members, project maintainers, or members of our project team, either online or offline. Violations of our Code of Conduct may be reported by contacting the project team at [ajv.validator@gmail.com](mailto:ajv.validator@gmail.com). The project team will review and investigate all complaints, to the best of our ability, and will respond in a way that it deems appropriate to the circumstances. Reports of violations will be investigated in a respectful, professional manner as promptly and confidentially as possible. We will have zero tolerance for intimidation or retaliation against anyone who raises a concern, makes a report or cooperates in an investigation around a violation of our code of conduct. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions or removal as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) ================================================ FILE: CONTRIBUTING.md ================================================ --- permalink: /contributing --- # Contributing guide Thank you for your help making Ajv better! Every contribution is appreciated. There are many areas where you can contribute. More than 100 people contributed to Ajv, and we would love to have you join the development. We welcome implementing new features that will benefit many users and ideas to improve our documentation. At Ajv, we are committed to creating more equitable and inclusive spaces for our community and team members to contribute to discussions that affect both this project and our ongoing work in the open source ecosystem. We strive to create an environment of respect and healthy discourse by setting standards for our interactions and we expect it from all members of our community - from long term project member to first time visitor. For more information, review our [code of conduct](./CODE_OF_CONDUCT.md) and values. ::: tip Submit issue first If you plan to implement a new feature or some other change please create an issue first, to make sure that your work is not lost. ::: [[toc]] ## Documentation Ajv has a lot of features and maintaining documentation takes time. If anything is unclear, or could be explained better, we appreciate the time you spend correcting or clarifying it. There is a link in the bottom of each website page to quickly edit it. ## Issues Before submitting the issue: - Search the existing issues - Review [Frequently Asked Questions](./docs/faq.md). - Provide all the relevant information, reducing both your schema and data to the smallest possible size when they still have the issue. We value simplicity - simplifying the example that shows the issue makes it more valuable for other users. This process helps us reduce situations where an error is occurring due to incorrect usage rather than a bug. ### Bug reports Please make sure to include the following information in the issue: 1. What version of Ajv are you using? 2. Does the issue happen if you use the latest version? 3. Ajv [options object](./docs/options) 4. Schema and the data you are validating (please make it as small as possible to reproduce the issue). 5. Your code sample (please use `options`, `schema` and `data` as variables). 6. Validation result, data AFTER validation, error messages. 7. What results did you expect? To speed up investigation and fixes, please include the link to the working code sample at runkit.com (please clone https://runkit.com/esp/ajv-issue). [Create bug report](https://github.com/ajv-validator/ajv/issues/new?template=bug-or-error-report.md). ### Security vulnerabilities To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues. ### Change proposals [Create a proposal](https://github.com/ajv-validator/ajv/issues/new?template=change.md) for a new feature, option or some other improvement. Please include this information: 1. The version of Ajv you are using. 2. The problem you want to solve. 3. Your solution to the problem. 4. Would you like to implement it? If you’re requesting a change, it would be helpful to include this as well: 1. What you did. 2. What happened. 3. What you would like to happen. Please include as much details as possible - the more information, the better. ### Browser and compatibility issues [Create an issue](https://github.com/ajv-validator/ajv/issues/new?template=compatibility.md) to report a compatibility problem that only happens in a particular environment (when your code works correctly in the latest stable Node.js in linux systems but fails in some other environment). Please include this information: 1. The version of Ajv you are using. 2. The environment you have the problem with. 3. Your code (please make it as small as possible to reproduce the issue). 4. If your issue is in the browser, please list the other packages loaded in the page in the order they are loaded. Please check if the issue gets resolved (or results change) if you move Ajv bundle closer to the top. 5. Results in the latest stable Node.js. 6. Results and error messages in your platform. ### Installation and dependency issues [Create an issue](https://github.com/ajv-validator/ajv/issues/new?template=installation.md) to report problems that happen during Ajv installation or when Ajv is missing some dependency. Before submitting the issue, please try the following: - use the latest stable Node.js and `npm` - try using `yarn` instead of `npm` - the issue can be related to https://github.com/npm/npm/issues/19877 - remove `node_modules` and `package-lock.json` and run `npm install` again If nothing helps, please submit: 1. The version of Ajv you are using 2. Operating system and Node.js version 3. Package manager and its version 4. Link to (or contents of) package.json and package-lock.json 5. Error messages 6. The output of `npm ls` ### Using JSON Schema standard Ajv implements JSON Schema standard draft-04 and draft-06/07. If it is a general issue related to using the standard keywords included in JSON Schema specification or implementing some advanced validation logic please ask the question on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=jsonschema,ajv) (my account is [esp](https://stackoverflow.com/users/1816503/esp)) or submit the question to [json-schema.org](https://github.com/json-schema-org/json-schema-spec/issues/new). Please mention @epoberezkin. ### Ajv usage questions The best place to ask a question about using Ajv is [Gitter chat](https://gitter.im/ajv-validator/ajv). If the question is advanced, it can be submitted to [Stack Overflow](http://stackoverflow.com/questions/ask?tags=jsonschema,ajv). ## Code Thanks a lot for considering contributing to Ajv! Our users have created many great features, and we look forward to your contributions. For help navigating the code, please review the [Code components](./docs/components.md) document. ### How we make decisions We value conscious curation of our library size, and balancing performance and functionality. To that end, we cannot accept every suggestion. When evaluating pull requests we consider: - Will this benefit many users or a niche use case? - How will this impact the performance of Ajv? - How will this expand our library size? To help us evaluate and understand, when you submit an issue and pull request: - Explain why this feature is important to the user base - Include documentation - Include test coverage with any new feature implementations Please include documentation and test coverage with any new feature implementations. ### Development Running tests: ```bash npm install git submodule update --init npm test ``` `npm run build` - compiles typescript to dist folder. `npm run watch` - automatically compiles typescript when files on lib folder changes. ### Pull requests We want to iterate on the code efficiently. To speed up the process, please follow these steps: 1. Submit an [issue with the bug](https://github.com/ajv-validator/ajv/issues/new) or with the proposed change (unless the contribution is to fix the documentation typos and mistakes). 2. Describe the proposed api and implementation plan (unless the issue is a relatively simple bug and fixing it doesn't change any api). 3. Once agreed, please write as little code as possible to achieve the desired result. We are passionate about keeping our library size optimized. 4. Please add the tests both for the added feature and, if you are submitting an option, for the existing behaviour when this option is turned off or not passed. 5. Please avoid unnecessary changes, refactoring or changing coding styles as part of your change (unless the change was proposed as refactoring). 6. Follow the coding conventions even if they are not validated. 7. Please run the tests before committing your code. 8. If tests fail in CI build after you make a PR please investigate and fix the issue. ### Contributions license When contributing the code you confirm that: 1. Your contribution is created by you. 2. You have the right to submit it under the MIT license. 3. You understand and agree that your contribution is public, will be stored indefinitely, can be redistributed as the part of Ajv or another related package under MIT license, modified or completely removed from Ajv. 4. You grant irrevocable MIT license to use your contribution as part of Ajv or any other package. 5. You waive all rights to your contribution. 6. Unless you request otherwise, you can be mentioned as the author of the contribution in the Ajv documentation and change log. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-2021 Evgeny Poberezkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Ajv logo   # Ajv JSON schema validator The fastest JSON validator for Node.js and browser. Supports JSON Schema draft-04/06/07/2019-09/2020-12 ([draft-04 support](https://ajv.js.org/json-schema.html#draft-04) requires ajv-draft-04 package) and JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). [![build](https://github.com/ajv-validator/ajv/actions/workflows/build.yml/badge.svg)](https://github.com/ajv-validator/ajv/actions?query=workflow%3Abuild) [![npm](https://img.shields.io/npm/v/ajv.svg)](https://www.npmjs.com/package/ajv) [![npm downloads](https://img.shields.io/npm/dm/ajv.svg)](https://www.npmjs.com/package/ajv) [![Coverage Status](https://coveralls.io/repos/github/ajv-validator/ajv/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv?branch=master) [![SimpleX](https://img.shields.io/badge/chat-on%20SimpleX-70F0F9)](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F8KvvURM6J38Gdq9dCuPswMOkMny0xCOJ%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAr8rPVRuMOXv6kwF2yUAap-eoVg-9ssOFCi1fIrxTUw0%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%224pwLRgWHU9tlroMWHz0uOg%3D%3D%22%7D) [![Gitter](https://img.shields.io/gitter/room/ajv-validator/ajv.svg)](https://gitter.im/ajv-validator/ajv) [![GitHub Sponsors](https://img.shields.io/badge/$-sponsors-brightgreen)](https://github.com/sponsors/epoberezkin) ## Ajv sponsors [Mozilla](https://www.mozilla.org)[](https://opencollective.com/ajv) [Microsoft](https://opensource.microsoft.com)[](https://opencollective.com/ajv)[](https://opencollective.com/ajv) [Retool](https://retool.com/?utm_source=sponsor&utm_campaign=ajv)[Tidelift](https://tidelift.com/subscription/pkg/npm-ajv?utm_source=npm-ajv&utm_medium=referral&utm_campaign=enterprise)[SimpleX](https://github.com/simplex-chat/simplex-chat)[](https://opencollective.com/ajv) ## Contributing More than 100 people contributed to Ajv, and we would love to have you join the development. We welcome implementing new features that will benefit many users and ideas to improve our documentation. Please review [Contributing guidelines](./CONTRIBUTING.md) and [Code components](https://ajv.js.org/components.html). ## Documentation All documentation is available on the [Ajv website](https://ajv.js.org). Some useful site links: - [Getting started](https://ajv.js.org/guide/getting-started.html) - [JSON Schema vs JSON Type Definition](https://ajv.js.org/guide/schema-language.html) - [API reference](https://ajv.js.org/api.html) - [Strict mode](https://ajv.js.org/strict-mode.html) - [Standalone validation code](https://ajv.js.org/standalone.html) - [Security considerations](https://ajv.js.org/security.html) - [Command line interface](https://ajv.js.org/packages/ajv-cli.html) - [Frequently Asked Questions](https://ajv.js.org/faq.html) ## Please [sponsor Ajv development](https://github.com/sponsors/epoberezkin) Since I asked to support Ajv development 40 people and 6 organizations contributed via GitHub and OpenCollective - this support helped receiving the MOSS grant! Your continuing support is very important - the funds will be used to develop and maintain Ajv once the next major version is released. Please sponsor Ajv via: - [GitHub sponsors page](https://github.com/sponsors/epoberezkin) (GitHub will match it) - [Ajv Open Collective](https://opencollective.com/ajv) Thank you. #### Open Collective sponsors ## Performance Ajv generates code to turn JSON Schemas into super-fast validation functions that are efficient for v8 optimization. Currently Ajv is the fastest and the most standard compliant validator according to these benchmarks: - [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark) - 50% faster than the second place - [jsck benchmark](https://github.com/pandastrike/jsck#benchmarks) - 20-190% faster - [z-schema benchmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html) - [themis benchmark](https://cdn.rawgit.com/playlyfe/themis/master/benchmark/results.html) Performance of different validators by [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark): [![performance](https://chart.googleapis.com/chart?chxt=x,y&cht=bhs&chco=76A4FB&chls=2.0&chbh=62,4,1&chs=600x416&chxl=-1:|ajv|@exodus/schemasafe|is-my-json-valid|djv|@cfworker/json-schema|jsonschema/=t:100,69.2,51.5,13.1,5.1,1.2)](https://github.com/ebdrup/json-schema-benchmark/blob/master/README.md#performance) ## Features - Ajv implements JSON Schema [draft-06/07/2019-09/2020-12](http://json-schema.org/) standards (draft-04 is supported in v6): - all validation keywords (see [JSON Schema validation keywords](https://ajv.js.org/json-schema.html)) - [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) extensions: - NEW: keyword [discriminator](https://ajv.js.org/json-schema.html#discriminator). - keyword [nullable](https://ajv.js.org/json-schema.html#nullable). - full support of remote references (remote schemas have to be added with `addSchema` or compiled to be available) - support of recursive references between schemas - correct string lengths for strings with unicode pairs - JSON Schema [formats](https://ajv.js.org/guide/formats.html) (with [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin). - [validates schemas against meta-schema](https://ajv.js.org/api.html#api-validateschema) - NEW: supports [JSON Type Definition](https://datatracker.ietf.org/doc/rfc8927/): - all keywords (see [JSON Type Definition schema forms](https://ajv.js.org/json-type-definition.html)) - meta-schema for JTD schemas - "union" keyword and user-defined keywords (can be used inside "metadata" member of the schema) - supports [browsers](https://ajv.js.org/guide/environments.html#browsers) and Node.js 10.x - current - [asynchronous loading](https://ajv.js.org/guide/managing-schemas.html#asynchronous-schema-loading) of referenced schemas during compilation - "All errors" validation mode with [option allErrors](https://ajv.js.org/options.html#allerrors) - [error messages with parameters](https://ajv.js.org/api.html#validation-errors) describing error reasons to allow error message generation - i18n error messages support with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n) package - [removing-additional-properties](https://ajv.js.org/guide/modifying-data.html#removing-additional-properties) - [assigning defaults](https://ajv.js.org/guide/modifying-data.html#assigning-defaults) to missing properties and items - [coercing data](https://ajv.js.org/guide/modifying-data.html#coercing-data-types) to the types specified in `type` keywords - [user-defined keywords](https://ajv.js.org/guide/user-keywords.html) - additional extension keywords with [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package - [\$data reference](https://ajv.js.org/guide/combining-schemas.html#data-reference) to use values from the validated data as values for the schema keywords - [asynchronous validation](https://ajv.js.org/guide/async-validation.html) of user-defined formats and keywords ## Install To install version 8: ``` npm install ajv ``` ## Getting started Try it in the Node.js REPL: https://runkit.com/npm/ajv In JavaScript: ```javascript // or ESM/TypeScript import import Ajv from "ajv" // Node.js require: const Ajv = require("ajv") const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} const schema = { type: "object", properties: { foo: {type: "integer"}, bar: {type: "string"}, }, required: ["foo"], additionalProperties: false, } const data = { foo: 1, bar: "abc", } const validate = ajv.compile(schema) const valid = validate(data) if (!valid) console.log(validate.errors) ``` Learn how to use Ajv and see more examples in the [Guide: getting started](https://ajv.js.org/guide/getting-started.html) ## Changes history See [https://github.com/ajv-validator/ajv/releases](https://github.com/ajv-validator/ajv/releases) **Please note**: [Changes in version 8.0.0](https://github.com/ajv-validator/ajv/releases/tag/v8.0.0) [Version 7.0.0](https://github.com/ajv-validator/ajv/releases/tag/v7.0.0) [Version 6.0.0](https://github.com/ajv-validator/ajv/releases/tag/v6.0.0). ## Code of conduct Please review and follow the [Code of conduct](./CODE_OF_CONDUCT.md). Please report any unacceptable behaviour to ajv.validator@gmail.com - it will be reviewed by the project team. ## Security contact To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues. ## Open-source software support Ajv is a part of [Tidelift subscription](https://tidelift.com/subscription/pkg/npm-ajv?utm_source=npm-ajv&utm_medium=referral&utm_campaign=readme) - it provides a centralised support to open-source software users, in addition to the support provided by software maintainers. ## License [MIT](./LICENSE) ================================================ FILE: benchmark/jtd.js ================================================ /* eslint-disable no-empty */ /* eslint-disable no-console */ const Ajv = require("ajv/dist/jtd") const Benchmark = require("benchmark") const jtdValidationTests = require("../spec/json-typedef-spec/tests/validation.json") const ajv = new Ajv() const suite = new Benchmark.Suite() const tests = [] for (const testName in jtdValidationTests) { const {schema, instance, errors} = jtdValidationTests[testName] const valid = errors.length === 0 if (!valid) continue tests.push({ validate: ajv.compile(schema), serialize: ajv.compileSerializer(schema), parse: ajv.compileParser(schema), data: instance, json: JSON.stringify(instance), }) } // suite.add("JTD test suite: compiled JTD serializers", () => { // for (const test of tests) { // test.serialize(test.data) // } // }) // suite.add("JTD test suite: JSON.stringify", () => { // for (const test of tests) { // JSON.stringify(test.data) // } // }) const testSchema = { definitions: { obj: { properties: { foo: {type: "string"}, bar: {type: "int8"}, }, }, }, properties: { a: {ref: "obj"}, }, optionalProperties: { b: {ref: "obj"}, }, } const testData = { a: { foo: "foo1", bar: 1, }, b: { foo: "foo2", bar: 2, }, } // const serializer = ajv.compileSerializer(testSchema) // suite.add("test data: compiled JTD serializer", () => serializer(testData)) // suite.add("test data: JSON.stringify", () => JSON.stringify(testData)) suite.add("JTD test suite: compiled JTD parsers", () => { for (const test of tests) { test.parse(test.json) } }) suite.add("JTD test suite: JSON.parse", () => { for (const test of tests) { JSON.parse(test.json) } }) suite.add("JTD test suite: JSON.parse + validate", () => { for (const test of tests) { JSON.parse(test.json) } }) const validTestData = JSON.stringify(testData) const invalidTestData = JSON.stringify({ a: { foo: "foo1", bar: "1", }, b: { foo: "foo2", bar: 2, }, }) const parse = ajv.compileParser(testSchema) const validate = ajv.compile(testSchema) suite.add("valid test data: compiled JTD parser", () => parse(validTestData)) suite.add("valid test data: JSON.parse", () => JSON.parse(validTestData)) suite.add("valid test data: JSON.parse + validate", () => validate(JSON.parse(validTestData))) suite.add("invalid test data: compiled JTD parser", () => parse(invalidTestData)) suite.add("invalid test data: JSON.parse", () => JSON.parse(invalidTestData)) suite.add("invalid test data: JSON.parse + validate", () => validate(JSON.parse(invalidTestData))) console.log() suite .on("cycle", (event) => console.log(String(event.target))) .on("complete", function () { // eslint-disable-next-line no-invalid-this console.log('The fastest is "' + this.filter("fastest").map("name") + '"') }) .run({async: true}) ================================================ FILE: benchmark/package.json ================================================ { "private": true, "devDependencies": { "benchmark": "^2.1.4" } } ================================================ FILE: bower.json ================================================ { "name": "ajv", "description": "Another JSON Schema Validator", "main": "bundle/ajv.min.js", "authors": ["Evgeny Poberezkin"], "license": "MIT", "keywords": ["JSON", "schema", "validator"], "homepage": "https://github.com/ajv-validator/ajv", "moduleType": ["amd", "globals", "node"], "ignore": ["node_modules", "bower_components", "spec"] } ================================================ FILE: docs/.vuepress/components/Button.vue ================================================ ================================================ FILE: docs/.vuepress/components/Column.vue ================================================ ================================================ FILE: docs/.vuepress/components/Columns.vue ================================================ ================================================ FILE: docs/.vuepress/components/Contributors.vue ================================================ ================================================ FILE: docs/.vuepress/components/Feature.vue ================================================ ================================================ FILE: docs/.vuepress/components/Features.vue ================================================ ================================================ FILE: docs/.vuepress/components/FooterColumn.vue ================================================ ================================================ FILE: docs/.vuepress/components/FooterColumns.vue ================================================ ================================================ FILE: docs/.vuepress/components/GitHub.vue ================================================ ================================================ FILE: docs/.vuepress/components/HeroSection.vue ================================================ ================================================ FILE: docs/.vuepress/components/HomePage.vue ================================================ ================================================ FILE: docs/.vuepress/components/HomeSection.vue ================================================ ================================================ FILE: docs/.vuepress/components/NewsHome.vue ================================================ ================================================ FILE: docs/.vuepress/components/NewsIndex.vue ================================================ ================================================ FILE: docs/.vuepress/components/NewsPost.vue ================================================ ================================================ FILE: docs/.vuepress/components/NewsPostMeta.vue ================================================ ================================================ FILE: docs/.vuepress/components/Projects.vue ================================================ ================================================ FILE: docs/.vuepress/components/Sponsors.vue ================================================ ================================================ FILE: docs/.vuepress/components/Subscribe.vue ================================================ ================================================ FILE: docs/.vuepress/components/Testimonial.vue ================================================ ================================================ FILE: docs/.vuepress/components/Testimonials.vue ================================================ ================================================ FILE: docs/.vuepress/config.js ================================================ const {slugify} = require("@vuepress/shared-utils") const title = "Ajv JSON schema validator" const description = "The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)" module.exports = { title, description, head: [ ["link", {rel: "icon", href: `/favicon.ico`}], ["meta", {charset: "utf-8"}], ["meta", {property: "og:title", content: title}], ["meta", {property: "og:description", content: description}], ["meta", {property: "og:image", content: "https://ajv.js.org/img/ajv.png"}], ["meta", {itemprop: "image", content: "https://ajv.js.org/img/ajv.png"}], ["meta", {name: "twitter:card", content: "summary"}], ["meta", {name: "twitter:title", content: title}], ["meta", {name: "twitter:image:src", content: "https://ajv.js.org/img/ajv.png"}], ["meta", {name: "apple-mobile-web-app-capable", content: "yes"}], ["link", {rel: "apple-touch-icon", href: `/img/apple-touch-icon.png`}], ], markdown: { slugify: (str) => slugify(str.replace(/]*\/>/, "")), toc: {includeLevel: [2, 3, 4]}, }, heroText: "hello there", themeConfig: { logo: "/img/ajv.svg", nav: [ {text: "Home", link: "/"}, { text: "Guide", items: [ {link: "/guide/why-ajv", text: "Why use Ajv"}, {link: "/guide/getting-started", text: "Getting started"}, {link: "/guide/typescript", text: "Using with TypeScript"}, {link: "/guide/schema-language", text: "Choosing schema language"}, {link: "/guide/managing-schemas", text: "Managing schemas"}, {link: "/guide/combining-schemas", text: "Combining schemas"}, {link: "/guide/formats", text: "Format validation"}, {link: "/guide/modifying-data", text: "Modifying data"}, {link: "/guide/user-keywords", text: "User-defined keywords"}, {link: "/guide/async-validation", text: "Asynchronous validation"}, {link: "/guide/environments", text: "Execution environments"}, ], }, { text: "Reference", items: [ {link: "/api", text: "API Reference"}, {link: "/options", text: "Ajv options"}, {link: "/json-schema", text: "JSON Schema"}, {link: "/json-type-definition", text: "JSON Type Definition"}, {link: "/strict-mode", text: "Strict mode"}, {link: "/standalone", text: "Standalone validation code"}, {link: "/keywords", text: "User defined keywords"}, {link: "/coercion", text: "Type coercion rules"}, ], }, { text: "Learn more", items: [ { text: "Extending Ajv", items: [ {link: "/packages/", text: "Extending Ajv"}, {link: "/packages/ajv-cli", text: "ajv-cli"}, {link: "/packages/ajv-errors", text: "ajv-errors"}, {link: "/packages/ajv-formats", text: "ajv-formats"}, {link: "/packages/ajv-i18n", text: "ajv-i18n"}, {link: "/packages/ajv-keywords", text: "ajv-keywords"}, ], }, { text: "Contributors", items: [ {link: "/contributing", text: "Contributing guide"}, {link: "/codegen", text: "Code generation design"}, {link: "/components", text: "Code components"}, {link: "/code_of_conduct", text: "Code of Conduct"}, ], }, { text: "Information", items: [ {link: "/news/", text: "News"}, {link: "/faq", text: "FAQ"}, {link: "/security", text: "Security"}, {link: "/v6-to-v8-migration", text: "Migrate from v6"}, {link: "/testimonials", text: "What users say"}, {link: "/license", text: "License"}, ], }, ], }, ], sidebar: [ { title: "Guide", children: [ "/guide/why-ajv", "/guide/getting-started", "/guide/typescript", "/guide/schema-language", "/guide/managing-schemas", "/guide/combining-schemas", "/guide/formats", "/guide/modifying-data", "/guide/user-keywords", "/guide/async-validation", "/guide/environments", ], }, { title: "Reference", children: [ "/api", "/options", "/json-schema", "/json-type-definition", "/strict-mode", "/standalone", "/keywords", "/coercion", ], }, { title: "Extending Ajv", children: [ ["/packages/", "Extending Ajv"], ["/packages/ajv-formats", "ajv-formats"], ["/packages/ajv-keywords", "ajv-keywords"], ["/packages/ajv-errors", "ajv-errors"], ["/packages/ajv-i18n", "ajv-i18n"], ["/packages/ajv-cli", "ajv-cli"], ], }, { title: "Contributors", children: [ "/contributing", "/codegen", "/components", ["/code_of_conduct", "Code of conduct"], ], }, { title: "Information", children: [ "/news/", "/faq", "/security", ["/v6-to-v8-migration", "Migrate from v6 to v8"], "/testimonials", ["/license", "License"], ], }, ], repo: "ajv-validator/ajv", docsDir: "docs", editLinks: true, activeHeaderLinks: false, }, } ================================================ FILE: docs/.vuepress/styles/index.styl ================================================ img + span > .icon.outbound { display: none; } body { font-family: 'Raleway'; font-weight: normal; } strong { font-weight: 550; } h1, h2, h3, h4, h5, h6 { font-family: 'IstokWeb'; font-weight: normal; } .custom-block.tip { border-color: $tipColor; background-color: $attentionBoxColor; color: $textColor; .custom-block-title { color: $textColor; } } .custom-block.warning { border-color: $warningColor; background-color: $attentionBoxColor; color: $textColor; .custom-block-title { color: $textColor; } } .custom-block.danger { border-color: $dangerColor; background-color: $attentionBoxColor; color: $textColor; .custom-block-title { color: $textColor; } } .sidebar nav.nav-links div.nav-item { display: none; } span.badge { font-family: 'Raleway'; font-weight: 500; } .theme-code-group .token.string { color: $accentCode; } .theme-code-group button.theme-code-group__nav-tab.theme-code-group__nav-tab-active { border-color: $accentCode; } .navbar span.site-name { font-family: IstokWeb; font-weight: 500; font-size: 1.6em; } p.sidebar-heading { font-weight: 500; font-size: 1em; } a.sidebar-link.active { font-weight: 500!important; } @font-face { font-family: 'Raleway'; src: url(/fonts/Raleway-VariableFont_wght.ttf); } @font-face { font-family: 'IstokWeb'; src: url(/fonts/IstokWeb-Regular.ttf); font-weight: normal; } ================================================ FILE: docs/.vuepress/styles/palette.styl ================================================ $ajvBlueColor = #409cff $ajvGreenColor = #23c8d2 // #1fdca3 $ajvRedColor = #f5775b $tipColor = $ajvGreenColor $warningColor = #f1f440 $dangerColor = $ajvRedColor $attentionBoxColor = #f7f7f3 $accentColor = #07aab4 // darken($ajvGreenColor, 15%) $accentCode = #7ec699 $textColor = #292828 $borderColor = #eaecef $codeBgColor = #282c34 $arrowBgColor = #ccc $badgeTipColor = $ajvGreenColor $badgeWarningColor = #e9c400 $badgeErrorColor = $ajvRedColor $MQMobileNarrow = 480px $MQMobileSmall = 414px ================================================ FILE: docs/.vuepress/theme/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2018-present, Yuxi (Evan) You Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: docs/.vuepress/theme/components/AlgoliaSearchBox.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/DropdownLink.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/DropdownTransition.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/Home.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/NavLink.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/NavLinks.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/Navbar.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/Page.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/PageEdit.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/PageNav.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/Sidebar.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/SidebarButton.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/SidebarGroup.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/SidebarLink.vue ================================================ ================================================ FILE: docs/.vuepress/theme/components/SidebarLinks.vue ================================================ ================================================ FILE: docs/.vuepress/theme/global-components/Badge.vue ================================================ ================================================ FILE: docs/.vuepress/theme/global-components/CodeBlock.vue ================================================ ================================================ FILE: docs/.vuepress/theme/global-components/CodeGroup.vue ================================================ ================================================ FILE: docs/.vuepress/theme/index.js ================================================ const path = require("path") // Theme API. module.exports = (options, ctx) => { const {themeConfig, siteConfig} = ctx // resolve algolia const isAlgoliaSearch = themeConfig.algolia || Object.keys((siteConfig.locales && themeConfig.locales) || {}).some( (base) => themeConfig.locales[base].algolia ) const enableSmoothScroll = themeConfig.smoothScroll === true return { alias() { return { "@AlgoliaSearchBox": isAlgoliaSearch ? path.resolve(__dirname, "components/AlgoliaSearchBox.vue") : path.resolve(__dirname, "noopModule.js"), } }, plugins: [ ["@vuepress/active-header-links", options.activeHeaderLinks], "@vuepress/search", "@vuepress/plugin-nprogress", [ "container", { type: "tip", defaultTitle: { "/": "TIP", "/zh/": "提示", }, }, ], [ "container", { type: "warning", defaultTitle: { "/": "WARNING", "/zh/": "注意", }, }, ], [ "container", { type: "danger", defaultTitle: { "/": "WARNING", "/zh/": "警告", }, }, ], [ "container", { type: "details", before: (info) => `
${info ? `${info}` : ""}\n`, after: () => "
\n", }, ], ["smooth-scroll", enableSmoothScroll], ], } } ================================================ FILE: docs/.vuepress/theme/layouts/404.vue ================================================ ================================================ FILE: docs/.vuepress/theme/layouts/Layout.vue ================================================ ================================================ FILE: docs/.vuepress/theme/noopModule.js ================================================ export default {} ================================================ FILE: docs/.vuepress/theme/styles/arrow.styl ================================================ @require './config' .arrow display inline-block width 0 height 0 &.up border-left 4px solid transparent border-right 4px solid transparent border-bottom 6px solid $arrowBgColor &.down border-left 4px solid transparent border-right 4px solid transparent border-top 6px solid $arrowBgColor &.right border-top 4px solid transparent border-bottom 4px solid transparent border-left 6px solid $arrowBgColor &.left border-top 4px solid transparent border-bottom 4px solid transparent border-right 6px solid $arrowBgColor ================================================ FILE: docs/.vuepress/theme/styles/code.styl ================================================ {$contentClass} code color lighten($textColor, 20%) padding 0.25rem 0.5rem margin 0 font-size 0.85em background-color rgba(27,31,35,0.05) border-radius 3px .token &.deleted color #EC5975 &.inserted color $accentColor {$contentClass} pre, pre[class*="language-"] line-height 1.4 padding 1.25rem 1.5rem margin 0.85rem 0 background-color $codeBgColor border-radius 6px overflow auto code color #fff padding 0 background-color transparent border-radius 0 div[class*="language-"] position relative background-color $codeBgColor border-radius 6px .highlight-lines user-select none padding-top 1.3rem position absolute top 0 left 0 width 100% line-height 1.4 .highlighted background-color rgba(0, 0, 0, 66%) pre, pre[class*="language-"] background transparent position relative z-index 1 &::before position absolute z-index 3 top 0.8em right 1em font-size 0.75rem color rgba(255, 255, 255, 0.4) &:not(.line-numbers-mode) .line-numbers-wrapper display none &.line-numbers-mode .highlight-lines .highlighted position relative &:before content ' ' position absolute z-index 3 left 0 top 0 display block width $lineNumbersWrapperWidth height 100% background-color rgba(0, 0, 0, 66%) pre padding-left $lineNumbersWrapperWidth + 1 rem vertical-align middle .line-numbers-wrapper position absolute top 0 width $lineNumbersWrapperWidth text-align center color rgba(255, 255, 255, 0.3) padding 1.25rem 0 line-height 1.4 br user-select none .line-number position relative z-index 4 user-select none font-size 0.85em &::after content '' position absolute z-index 2 top 0 left 0 width $lineNumbersWrapperWidth height 100% border-radius 6px 0 0 6px border-right 1px solid rgba(0, 0, 0, 66%) background-color $codeBgColor for lang in $codeLang div{'[class~="language-' + lang + '"]'} &:before content ('' + lang) div[class~="language-javascript"] &:before content "js" div[class~="language-typescript"] &:before content "ts" div[class~="language-markup"] &:before content "html" div[class~="language-markdown"] &:before content "md" div[class~="language-json"]:before content "json" div[class~="language-ruby"]:before content "rb" div[class~="language-python"]:before content "py" div[class~="language-bash"]:before content "sh" div[class~="language-php"]:before content "php" @import '~prismjs/themes/prism-tomorrow.css' ================================================ FILE: docs/.vuepress/theme/styles/config.styl ================================================ $contentClass = '.theme-default-content' ================================================ FILE: docs/.vuepress/theme/styles/custom-blocks.styl ================================================ .custom-block .custom-block-title font-weight 600 margin-bottom -0.4rem &.tip, &.warning, &.danger padding .1rem 1.5rem border-left-width .5rem border-left-style solid margin 1rem 0 &.tip background-color #f3f5f7 border-color #42b983 &.warning background-color rgba(255,229,100,.3) border-color darken(#ffe564, 35%) color darken(#ffe564, 70%) .custom-block-title color darken(#ffe564, 50%) a color $textColor &.danger background-color #ffe6e6 border-color darken(red, 20%) color darken(red, 70%) .custom-block-title color darken(red, 40%) a color $textColor &.details display block position relative border-radius 2px margin 1.6em 0 padding 1.6em background-color #eee h4 margin-top 0 figure, p &:last-child margin-bottom 0 padding-bottom 0 summary outline none cursor pointer ================================================ FILE: docs/.vuepress/theme/styles/index.styl ================================================ @require './config' @require './code' @require './custom-blocks' @require './arrow' @require './wrapper' @require './toc' html, body padding 0 margin 0 background-color #fff body font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif -webkit-font-smoothing antialiased -moz-osx-font-smoothing grayscale font-size 16px color $textColor .page padding-left $sidebarWidth .navbar position fixed z-index 20 top 0 left 0 right 0 height $navbarHeight background-color #fff box-sizing border-box border-bottom 1px solid $borderColor .sidebar-mask position fixed z-index 9 top 0 left 0 width 100vw height 100vh display none .sidebar font-size 16px background-color #fff width $sidebarWidth position fixed z-index 10 margin 0 top $navbarHeight left 0 bottom 0 box-sizing border-box border-right 1px solid $borderColor overflow-y auto {$contentClass}:not(.custom) @extend $wrapper > *:first-child margin-top $navbarHeight a:hover text-decoration underline p.demo padding 1rem 1.5rem border 1px solid #ddd border-radius 4px img max-width 100% {$contentClass}.custom padding 0 margin 0 img max-width 100% a font-weight 500 color $accentColor text-decoration none p a code font-weight 400 color $accentColor kbd background #eee border solid 0.15rem #ddd border-bottom solid 0.25rem #ddd border-radius 0.15rem padding 0 0.15em blockquote font-size 1rem color #999; border-left .2rem solid #dfe2e5 margin 1rem 0 padding .25rem 0 .25rem 1rem & > p margin 0 ul, ol padding-left 1.2em strong font-weight 600 h1, h2, h3, h4, h5, h6 font-weight 600 line-height 1.25 {$contentClass}:not(.custom) > & margin-top (0.5rem - $navbarHeight) padding-top ($navbarHeight + 1rem) margin-bottom 0 &:first-child margin-top -1.5rem margin-bottom 1rem + p, + pre, + .custom-block margin-top 2rem &:focus .header-anchor, &:hover .header-anchor opacity: 1 h1 font-size 2.2rem h2 font-size 1.65rem padding-bottom .3rem border-bottom 1px solid $borderColor h3 font-size 1.35rem a.header-anchor font-size 0.85em float left margin-left -0.87em padding-right 0.23em margin-top 0.125em opacity 0 &:focus, &:hover text-decoration none code, kbd, .line-number font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace p, ul, ol line-height 1.7 hr border 0 border-top 1px solid $borderColor table border-collapse collapse margin 1rem 0 display: block overflow-x: auto tr border-top 1px solid #dfe2e5 &:nth-child(2n) background-color #f6f8fa th, td border 1px solid #dfe2e5 padding .6em 1em .theme-container &.sidebar-open .sidebar-mask display: block &.no-navbar {$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6 margin-top 1.5rem padding-top 0 .sidebar top 0 @media (min-width: ($MQMobile + 1px)) .theme-container.no-sidebar .sidebar display none .page padding-left 0 @require 'mobile.styl' ================================================ FILE: docs/.vuepress/theme/styles/mobile.styl ================================================ @require './config' $mobileSidebarWidth = $sidebarWidth * 0.82 // narrow desktop / iPad @media (max-width: $MQNarrow) .sidebar font-size 15px width $mobileSidebarWidth .page padding-left $mobileSidebarWidth // wide mobile @media (max-width: $MQMobile) .sidebar top 0 padding-top $navbarHeight transform translateX(-100%) transition transform .2s ease .page padding-left 0 .theme-container &.sidebar-open .sidebar transform translateX(0) &.no-navbar .sidebar padding-top: 0 // narrow mobile @media (max-width: $MQMobileNarrow) h1 font-size 1.9rem {$contentClass} div[class*="language-"] margin 0.85rem -1.5rem border-radius 0 ================================================ FILE: docs/.vuepress/theme/styles/toc.styl ================================================ .table-of-contents .badge vertical-align middle ================================================ FILE: docs/.vuepress/theme/styles/wrapper.styl ================================================ $wrapper max-width $contentWidth margin 0 auto padding 2rem 2.5rem @media (max-width: $MQNarrow) padding 2rem @media (max-width: $MQMobileNarrow) padding 1.5rem ================================================ FILE: docs/.vuepress/theme/util/index.js ================================================ export const hashRE = /#.*$/ export const extRE = /\.(md|html)$/ export const endingSlashRE = /\/$/ export const outboundRE = /^[a-z]+:/i export function normalize(path) { return decodeURI(path).replace(hashRE, "").replace(extRE, "") } export function getHash(path) { const match = path.match(hashRE) if (match) { return match[0] } } export function isExternal(path) { return outboundRE.test(path) } export function isMailto(path) { return /^mailto:/.test(path) } export function isTel(path) { return /^tel:/.test(path) } export function ensureExt(path) { if (isExternal(path)) { return path } const hashMatch = path.match(hashRE) const hash = hashMatch ? hashMatch[0] : "" const normalized = normalize(path) if (endingSlashRE.test(normalized)) { return path } return normalized + ".html" + hash } export function isActive(route, path) { const routeHash = decodeURIComponent(route.hash) const linkHash = getHash(path) if (linkHash && routeHash !== linkHash) { return false } const routePath = normalize(route.path) const pagePath = normalize(path) return routePath === pagePath } export function resolvePage(pages, rawPath, base) { if (isExternal(rawPath)) { return { type: "external", path: rawPath, } } if (base) { rawPath = resolvePath(rawPath, base) } const path = normalize(rawPath) for (let i = 0; i < pages.length; i++) { if (normalize(pages[i].regularPath) === path) { return Object.assign({}, pages[i], { type: "page", path: ensureExt(pages[i].path), }) } } console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) return {} } function resolvePath(relative, base, append) { const firstChar = relative.charAt(0) if (firstChar === "/") { return relative } if (firstChar === "?" || firstChar === "#") { return base + relative } const stack = base.split("/") // remove trailing segment if: // - not appending // - appending to trailing slash (last segment is empty) if (!append || !stack[stack.length - 1]) { stack.pop() } // resolve relative path const segments = relative.replace(/^\//, "").split("/") for (let i = 0; i < segments.length; i++) { const segment = segments[i] if (segment === "..") { stack.pop() } else if (segment !== ".") { stack.push(segment) } } // ensure leading slash if (stack[0] !== "") { stack.unshift("") } return stack.join("/") } /** * @param { Page } page * @param { string } regularPath * @param { SiteData } site * @param { string } localePath * @returns { SidebarGroup } */ export function resolveSidebarItems(page, regularPath, site, localePath) { const {pages, themeConfig} = site const localeConfig = localePath && themeConfig.locales ? themeConfig.locales[localePath] || themeConfig : themeConfig const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar if (pageSidebarConfig === "auto") { return resolveHeaders(page) } const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar if (!sidebarConfig) { return [] } else { const {base, config} = resolveMatchingConfig(regularPath, sidebarConfig) if (config === "auto") { return resolveHeaders(page) } return config ? config.map((item) => resolveItem(item, pages, base)) : [] } } /** * @param { Page } page * @returns { SidebarGroup } */ function resolveHeaders(page) { const headers = groupHeaders(page.headers || []) return [ { type: "group", collapsable: false, title: page.title, path: null, children: headers.map((h) => ({ type: "auto", title: h.title, basePath: page.path, path: page.path + "#" + h.slug, children: h.children || [], })), }, ] } export function groupHeaders(headers) { // group h3s under h2 headers = headers.map((h) => Object.assign({}, h)) let lastH2 headers.forEach((h) => { if (h.level === 2) { lastH2 = h } else if (lastH2) { ;(lastH2.children || (lastH2.children = [])).push(h) } }) return headers.filter((h) => h.level === 2) } export function resolveNavLinkItem(linkItem) { return Object.assign(linkItem, { type: linkItem.items && linkItem.items.length ? "links" : "link", }) } /** * @param { Route } route * @param { Array | Array | [link: string]: SidebarConfig } config * @returns { base: string, config: SidebarConfig } */ export function resolveMatchingConfig(regularPath, config) { if (Array.isArray(config)) { return { base: "/", config: config, } } for (const base in config) { if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) { return { base, config: config[base], } } } return {} } function ensureEndingSlash(path) { return /(\.html|\/)$/.test(path) ? path : path + "/" } function resolveItem(item, pages, base, groupDepth = 1) { if (typeof item === "string") { return resolvePage(pages, item, base) } else if (Array.isArray(item)) { return Object.assign(resolvePage(pages, item[0], base), { title: item[1], }) } else { const children = item.children || [] if (children.length === 0 && item.path) { return Object.assign(resolvePage(pages, item.path, base), { title: item.title, }) } return { type: "group", path: item.path, title: item.title, sidebarDepth: item.sidebarDepth, initialOpenGroupIndex: item.initialOpenGroupIndex, children: children.map((child) => resolveItem(child, pages, base, groupDepth + 1)), collapsable: item.collapsable !== false, } } } ================================================ FILE: docs/README.md ================================================ --- homepage: true sidebar: false --- # Ajv JSON schema validator ## Security and reliability for JavaScript applications ### Write less code Ensure your data is valid once it is received ### Super fast & secure Compiles your schemas to optimized JavaScript code ### Multi-standard Use JSON Type Definition or JSON Schema ## Ajv sponsors [![mozilla](/img/mozilla.svg)](https://www.mozilla.org) [![reserved](/img/reserved.svg)](https://opencollective.com/ajv) [![microsoft](/img/microsoft.png)](https://opensource.microsoft.com) [![reserved](/img/reserved.svg)](https://opencollective.com/ajv) [![reserved](/img/reserved.svg)](https://opencollective.com/ajv) [![Retool](/img/retool.svg)](https://retool.com/?utm_source=sponsor&utm_campaign=ajv) [![Tidelift](/img/tidelift.svg)](https://tidelift.com/subscription/pkg/npm-ajv?utm_source=npm-ajv&utm_medium=referral&utm_campaign=enterprise) [![SimpleX](/img/simplex.svg)](https://github.com/simplex-chat/simplex-chat) [![reserved](/img/reserved.svg)](https://opencollective.com/ajv) Ajv is used by a large number of JavaScript applications and libraries in all JavaScript environments - Node.js, browser, Electron apps, WeChat mini-apps etc. It allows implementing complex data validation logic via declarative schemas for your JSON data, without writing code. Out of the box, Ajv supports [JSON Schema](./json-schema.md) (drafts 04, 06, 07, 2019-09 and 2020-12) and [JSON Type Definition](./json-type-definition.md) ([RFC8927](https://datatracker.ietf.org/doc/rfc8927/)).

```javascript const Ajv = require("ajv") const ajv = new Ajv() const schema = { type: "object", properties: { foo: {type: "integer"}, bar: {type: "string"} }, required: ["foo"], additionalProperties: false } const data = {foo: 1, bar: "abc"} const valid = ajv.validate(schema, data) if (!valid) console.log(ajv.errors) ``` ```javascript const Ajv = require("ajv/dist/jtd") const ajv = new Ajv() const schema = { properties: { foo: {type: "int32"} }, optionalProperties: { bar: {type: "string"} } } const data = {foo: 1, bar: "abc"} const valid = ajv.validate(schema, data) if (!valid) console.log(ajv.errors) ```
## What users say Ajv stands out as the implementation of choice - it provides a rich API which many thousands of people use in production... Ajv is partly responsible for the success of JSON Schema. [Ben Hutton](https://github.com/relequestual), JSON Schema Specification Lead [ESLint](https://eslint.org/) has used Ajv for validating our complex configurations. Ajv has proven to be reliable over the years we’ve been using it and ESLint is proud to sponsor Ajv’s continued development. [Nicholas C. Zakas](https://github.com/nzakas), ESLint creator and TSC member [All quotes](./testimonials.md) ## News ## Who uses Ajv [![eslint](./projects/eslint.png)](https://eslint.org) [![stoplight](./projects/stoplight.png)](https://stoplight.io) [![webpack](./projects/webpack.svg)](https://webpack.js.org) [table](https://github.com/gajus/table) [![fastify](./projects/fastify.png)](https://www.fastify.io) [restbase](https://github.com/wikimedia/restbase) [objection.js](https://github.com/vincit/objection.js) [![Taskcluster](./projects/taskcluster.png)](https://taskcluster.net) [![RxDB](./projects/rxdb.svg)](https://rxdb.info) [![react-jsonschema-form](./projects/rjsf.png)](https://github.com/rjsf-team/react-jsonschema-form) [![autorest](./projects/autorest.png)](https://github.com/Azure/autorest) [![node-red](./projects/nodered.png)](https://github.com/node-red/node-red) [![MDN](./projects/mdn.svg)](https://developer.mozilla.org) [![quicktype](./projects/quicktype.svg)](https://github.com/quicktype/quicktype) [![vue-form-generator](./projects/vue-form-generator.png)](https://github.com/vue-generators/vue-form-generator) [![teambit](./projects/teambit.png)](https://github.com/teambit/bit) [React Page](https://react-page.github.io) [![Backstage](./projects/backstage.svg)](https://backstage.io) [![rushstack](./projects/rushstack.svg)](https://github.com/microsoft/rushstack) [JupyterLab](https://github.com/jupyterlab/jupyterlab) [![0x](./projects/0x.png)](https://github.com/davidmarkclements/0x) [Plank.js](https://piqnt.com/planck.js/) [![fast](./projects/fast.svg)](https://www.fast.design) [![netlify cms](./projects/netlify-cms.png)](https://www.netlifycms.org) [![ng-alain](./projects/ng-alain.svg)](https://ng-alain.com/en) [![Vercel](./projects/vercel.svg)](https://vercel.com) [![AWS Amplify](./projects/aws-amplify.png)](https://github.com/aws-amplify/amplify-cli) [![FB flipper](./projects/flipper.png)](https://github.com/facebook/flipper) [![nx.dev](./projects/nx.svg)](https://nx.dev) [![express gateway](./projects/express-gateway.svg)](https://www.express-gateway.io) [![zigbee2mqtt](./projects/zigbee2mqtt.png)](https://www.zigbee2mqtt.io) [![dependency-cruiser](./projects/dependency-cruiser.png)](https://github.com/sverweij/dependency-cruiser) [![theia](./projects/theia.svg)](https://theia-ide.org) [![TSDoc](./projects/tsdoc.svg)](https://tsdoc.org) [![webhint](./projects/webhint.jpg)](https://webhint.io) [Vega-Lite](https://vega.github.io/vega-lite/) [![middy](./projects/middy.png)](https://middy.js.org) [JSDoc](https://github.com/jsdoc/jsdoc) [![Ts.ED](./projects/tsed.png)](https://tsed.io) ## Contributors Ajv is free to use and open-source that many developers contributed to. Join us! ![ajv](/img/ajv.svg) [Learn Ajv](./guide/getting-started.md) [Reference](./api.md) [Security](./security.md) [JSON Schema](./json-schema.md) [JSON Type Definition](./json-type-definition.md) [Contributing](./contributing.md) [![mozilla](/img/mozilla.svg)](https://www.mozilla.org) [![reserved](/img/reserved.svg)](https://opencollective.com/ajv) [© 2015-2021](./license.md) | Ajv JSON schema validator | [ajv.validator@gmail.com](mailto:ajv.validator@gmail.com) ================================================ FILE: docs/api.md ================================================ # API Reference [[toc]] ## Ajv constructor and methods ### new Ajv(options: object) Create Ajv instance: ```javascript const ajv = new Ajv() ``` See [Options](./options) ### ajv.compile(schema: object): (data: any) => boolean | Promise < any > Generate validating function and cache the compiled schema for future use. Validating function returns a boolean value (or promise for async schemas that must have `$async: true` property - see [Asynchronous validation](./guide/async-validation.md)). This function has properties `errors` and `schema`. Errors encountered during the last validation are assigned to `errors` property (it is assigned `null` if there was no errors). `schema` property contains the reference to the original schema. The schema passed to this method will be validated against meta-schema unless `validateSchema` option is false. If schema is invalid, an error will be thrown. See [options](#options). In typescript returned validation function can be a type guard if you pass type parameter: ```typescript interface Foo { foo: number } const FooSchema: JSONSchemaType = { type: "object", properties: {foo: {type: "number"}}, required: ["foo"], additionalProperties: false, } const validate = ajv.compile(FooSchema) // type of validate extends `(data: any) => data is Foo` const data: any = {foo: 1} if (validate(data)) { // data is Foo here console.log(data.foo) } else { console.log(validate.errors) } ``` See more advanced example in [the test](https://github.com/ajv-validator/ajv/blob/master/spec/types/json-schema.spec.ts). ### ajv.compileSerializer(schema: object): (data: any) => string Generate serializing function based on the [JTD schema](./json-type-definition.md) (caches the schema) - only in JTD instance of Ajv (see example below). Serializers compiled from JTD schemas can be more than 10 times faster than using `JSON.stringify`, because they do not traverse all the data, only the properties that are defined in the schema. Properties not defined in the schema will not be included in serialized JSON, unless the schema has `additionalProperties: true` flag. It can also be beneficial from the application security point of view, as it prevents leaking accidentally/temporarily added additional properties to the API responses. If you use JTD with typescript, the type for the schema can be derived from the data type, and generated serializer would only accept correct data type in this case: ```typescript import Ajv, {JTDSchemaType} from "ajv/dist/jtd" const ajv = new Ajv() interface MyData { foo: number bar?: string } const mySchema: JTDSchemaType = { properties: { foo: {type: "int32"} // any JTD number type would be accepted here }, optionalProperties: { bar: {type: "string"} } } const serializeMyData = ajv.compileSerializer(mySchema) // serializeMyData has type (x: MyData) => string // it prevents you from accidentally passing the wrong type ``` ::: warning Compiled serializers do NOT validate data! It is assumed that the data is valid according to the schema. ::: ### ajv.compileParser(schema: object): (json: string) => any Generate parsing function based on the [JTD schema](./json-type-definition.md) (caches the schema) - only in JTD instance of Ajv (see example below). Parsers compiled from JTD schemas have comparable performance to `JSON.parse`\* in case JSON string is valid according to the schema (and they do not just parse JSON - they ensure that parsed JSON is valid according to the schema as they parse), but they can be many times faster in case the string is invalid - for example, if schema expects an object, and JSON string is array the parser would fail on the first character. Parsing will fail if there are properties not defined in the schema, unless the schema has `additionalProperties: true` flag. If you use JTD with typescript, the type for the schema can be derived from the data type, and generated parser will return correct data type (see definitions example in the [serialize](#jtd-serialize) section): ```typescript const parseMyData = ajv.compileParser(mySchema) // parseMyData has type (s: string) => MyData | undefined // it returns correct data type in case parsing is successful and undefined if not const validData = parseMyData('{"foo":1}') // {foo: 1} - success const invalidData = parseMyData('{"x":1}') // undefined - failure console.log(parseMyData.position) // 4 console.log(parseMyData.message) // property x not allowed ``` \* As long as empty schema `{}` is not used - there is a possibility to improve performance in this case. Also, the performance of parsing `discriminator` schemas depends on the position of discriminator tag in the schema - the best parsing performance will be achieved if the tag is the first property - this is how compiled JTD serializers generate JSON in case of discriminator schemas. ### ajv.compileAsync(schema: object, meta?: boolean): Promise < Function > Asynchronous version of `compile` method that loads missing remote schemas using asynchronous function in `options.loadSchema`. This function returns a Promise that resolves to a validation function. An optional callback passed to `compileAsync` will be called with 2 parameters: error (or null) and validating function. The returned promise will reject (and the callback will be called with an error) when: - missing schema can't be loaded (`loadSchema` returns a Promise that rejects). - a schema containing a missing reference is loaded, but the reference cannot be resolved. - schema (or some loaded/referenced schema) is invalid. The function compiles schema and loads the first missing schema (or meta-schema) until all missing schemas are loaded. You can asynchronously compile meta-schema by passing `true` as the second parameter. Similarly to `compile`, it can return type guard in typescript. See example in [Asynchronous schema loading](./guide/managing-schemas.md#asynchronous-schema-loading). ### ajv.validate(schemaOrRef: object | string, data: any): boolean Validate data using passed schema (it will be compiled and cached). Instead of the schema you can use the key that was previously passed to `addSchema`, the schema id if it was present in the schema or any previously resolved reference. Validation errors will be available in the `errors` property of Ajv instance (`null` if there were no errors). In typescript this method can act as a type guard (similarly to function returned by `compile` method - see example there). ::: warning Save errors property Every time this method is called the errors are overwritten so you need to copy them to another variable if you want to use them later. ::: If the schema is asynchronous (has `$async` keyword on the top level) this method returns a Promise. See [Asynchronous validation](./guide/async-validation.md). ### ajv.addSchema(schema: object | object[], key?: string): Ajv Add schema(s) to validator instance. This method does not compile schemas (but it still validates them). Because of that dependencies can be added in any order and circular dependencies are supported. It also prevents unnecessary compilation of schemas that are containers for other schemas but not used as a whole. Array of schemas can be passed (schemas should have ids), the second parameter will be ignored. Key can be passed that can be used to reference the schema and will be used as the schema id if there is no id inside the schema. If the key is not passed, the schema id will be used as the key. Once the schema is added, it (and all the references inside it) can be referenced in other schemas and used to validate data. Although `addSchema` does not compile schemas, explicit compilation is not required - the schema will be compiled when it is used first time. By default the schema is validated against meta-schema before it is added, and if the schema does not pass validation the exception is thrown. This behaviour is controlled by `validateSchema` option. ::: tip Method chaining Ajv returns its instance for chaining from all methods prefixed `add*` and `remove*`: ```javascript const validate = new Ajv().addSchema(schema).addFormat(name, regex).getSchema(uri) ``` ::: ### ajv.addMetaSchema(schema: object | object[], key?: string): Ajv Adds meta schema(s) that can be used to validate other schemas. That function should be used instead of `addSchema` because there may be instance options that would compile a meta schema incorrectly (at the moment it is `removeAdditional` option). There is no need to explicitly add draft-07 meta schema (http://json-schema.org/draft-07/schema) - it is added by default, unless option `meta` is set to `false`. You only need to use it if you have a changed meta-schema that you want to use to validate your schemas. See `validateSchema`. ### ajv.validateSchema(schema: object): boolean Validates schema. This method should be used to validate schemas rather than `validate` due to the inconsistency of `uri` format in JSON Schema standard. By default this method is called automatically when the schema is added, so you rarely need to use it directly. If schema doesn't have `$schema` property, it is validated against draft 6 meta-schema (option `meta` should not be false). If schema has `$schema` property, then the schema with this id (that should be previously added) is used to validate passed schema. Errors will be available at `ajv.errors`. ### ajv.getSchema(key: string): undefined | ((data: any) => boolean | Promise < any >) Retrieve compiled schema previously added with `addSchema` by the key passed to `addSchema` or by its full reference (id). The returned validating function has `schema` property with the reference to the original schema. ### ajv.removeSchema(schemaOrRef: object | string | RegExp): Ajv Remove added/cached schema. Even if schema is referenced by other schemas it can be safely removed as dependent schemas have local references. Schema can be removed using: - key passed to `addSchema` - it's full reference (id) - RegExp that should match schema id or key (meta-schemas won't be removed) - actual schema object (that will be optionally serialized) to remove schema from cache If no parameter is passed all schemas but meta-schemas will be removed and the cache will be cleared. ### ajv.addFormat(name: string, format: Format): Ajv ```typescript type Format = | true // to ignore this format (and pass validation) | string // will be converted to RegExp | RegExp | (data: string) => boolean | Object // format definition (see below and in types) ``` Add format to validate strings or numbers. If object is passed it should have properties `validate`, `compare` and `async`: ```typescript interface FormatDefinition { // actual type definition is more precise - see types.ts validate: string | RegExp | (data: number | string) => boolean | Promise compare: (data1: string, data2: string): number // an optional function that accepts two strings // and compares them according to the format meaning. // This function is used with keywords `formatMaximum`/`formatMinimum` // (defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package). // It should return `1` if the first value is bigger than the second value, // `-1` if it is smaller and `0` if it is equal. async?: true // if `validate` is an asynchronous function type?: "string" | "number" // "string" is default. If data type is different, the validation will pass. } ``` Formats can be also added via `formats` option. ### ajv.addKeyword(definition: string | object): Ajv Add validation keyword to Ajv instance. Keyword should be different from all standard JSON Schema keywords and different from previously defined keywords. There is no way to redefine keywords or to remove keyword definition from the instance. Keyword must start with an ASCII letter, `_` or `$`, and may continue with ASCII letters, numbers, `_`, `$`, `-`, or `:`. It is recommended to use an application-specific prefix for keywords to avoid current and future name collisions. Example Keywords: - `"xyz-example"`: valid, and uses prefix for the xyz project to avoid name collisions. - `"example"`: valid, but not recommended as it may collide with future versions of JSON Schema etc. - `"3-example"`: invalid as numbers are not allowed to be the first character in a keyword Keyword definition is an object with the following properties: ```typescript interface KeywordDefinition { // actual type definition is more precise - see types.ts keyword: string // keyword name type?: string | string[] // JSON data type(s) the keyword applies to. Default - all types. schemaType?: string | string[] // the required schema JSON type code?: Function // function to generate code, used for all pre-defined keywords validate?: Function // validating function compile?: Function // compiling function macro?: Function // macro function error?: object // error definition object - see types.ts schema?: false // used with "validate" keyword to not pass schema to function metaSchema?: object // meta-schema for keyword schema dependencies?: string[] // properties that must be present in the parent schema - // it will be checked during schema compilation implements?: string[] // keyword names to reserve that this keyword implements modifying?: true // MUST be passed if keyword modifies data valid?: boolean // to pre-define validation result, validation function result will be ignored - // this option MUST NOT be used with `macro` keywords. $data?: true // to support [\$data reference](./guide/combining-schemas.md#data-reference) as the value of keyword. // The reference will be resolved at validation time. If the keyword has meta-schema, // it would be extended to allow $data and it will be used to validate the resolved value. // Supporting $data reference requires that keyword has `code` or `validate` function // (the latter can be used in addition to `compile` or `macro`). $dataError?: object // error definition object for invalid \$data schema - see types.ts async?: true // if the validation function is asynchronous // (whether it is returned from `compile` or passed in `validate` property). // It should return a promise that resolves with a value `true` or `false`. // This option is ignored in case of "macro" and "code" keywords. errors?: boolean | "full" // whether keyword returns errors. // If this property is not passed Ajv will determine // if the errors were set in case of failed validation. } ``` If only the property `keyword` is provided in the definition object, you can also pass the keyword name as the argument. `compile`, `macro` and `code` are mutually exclusive, only one should be used at a time. `validate` can be used separately or in addition to `compile` or `macro` to support [\$data reference](./guide/combining-schemas.md#data-reference). ::: tip Keyword is validated only for applicable data types If the keyword is validating data type that is different from the type(s) in its definition, the validation function will not be called (and expanded macro will not be used), so there is no need to check for data type inside validation function or inside schema returned by macro function (unless you want to enforce a specific type and for some reason do not want to use a separate `type` keyword for that). In the same way as standard keywords work, if the keyword does not apply to the data type being validated, the validation of this keyword will succeed. ::: See [User defined keywords](./keywords.md) for more details. ### ajv.getKeyword(keyword: string): object | boolean Returns keyword definition, `false` if the keyword is unknown. ### ajv.removeKeyword(keyword: string): Ajv Removes added or pre-defined keyword so you can redefine them. While this method can be used to extend pre-defined keywords, it can also be used to completely change their meaning - it may lead to unexpected results. ::: warning Compiled schemas and removed keywords The schemas compiled before the keyword is removed will continue to work without changes. To recompile schemas use `removeSchema` method and compile them again. ::: ### ajv.errorsText(errors?: object[], options?: object): string Returns the text with all errors in a String. Options can have properties `separator` (string used to separate errors, ", " by default) and `dataVar` (the variable name that instancePath is prefixed with, "data" by default). ## Validation errors In case of validation failure, Ajv assigns the array of errors to `errors` property of validation function (or to `errors` property of Ajv instance when `validate` or `validateSchema` methods were called). In case of [asynchronous validation](./guide/async-validation.md), the returned promise is rejected with exception `Ajv.ValidationError` that has `errors` property. ### Error objects Each reported error is an object with the following properties: ```typescript interface ErrorObject { keyword: string // validation keyword. instancePath: string // JSON Pointer to the location in the data instance (e.g., `"/prop/1/subProp"`). schemaPath: string // JSON Pointer to the location of the failing keyword in the schema params: object // type is defined by keyword value, see below // params property is the object with the additional information about error // it can be used to generate error messages // (e.g., using [ajv-i18n](https://github.com/ajv-validator/ajv-i18n) package). // See below for parameters set by all keywords. propertyName?: string // set for errors in `propertyNames` keyword schema. // `instancePath` still points to the object in this case. message?: string // the error message (can be excluded with option `messages: false`). // Options below are added with `verbose` option: schema?: any // the value of the failing keyword in the schema. parentSchema?: object // the schema containing the keyword. data?: any // the data validated by the keyword. } ``` For [JTD](./json-type-definition.md) schemas `instancePath` and `schemaPath` depend on the nature of the failure - the errors are consistent with [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). ### Error parameters Properties of `params` object in errors depend on the keyword that failed validation. In typescript, the ErrorObject is a discriminated union that allows to determine the type of error parameters based on the value of keyword: ```typescript const ajv = new Ajv() const validate = ajv.compile(schema) if (validate(data)) { // data is MyData here // ... } else { // DefinedError is a type for all pre-defined keywords errors, // validate.errors has type ErrorObject[] - to allow user-defined keywords with any error parameters. // Users can extend DefinedError to include the keywords errors they defined. for (const err of validate.errors as DefinedError[]) { switch (err.keyword) { case "maximum": console.log(err.limit) break case "pattern": console.log(err.pattern) break // ... } } } ``` Also see an example in [this test](https://github.com/ajv-validator/ajv/blob/master/spec/types/error-parameters.spec.ts) - `maxItems`, `minItems`, `maxLength`, `minLength`, `maxProperties`, `minProperties`: ```typescript type ErrorParams = {limit: number} // keyword value ``` - `additionalItems`: ```typescript // when `items` is an array of schemas and `additionalItems` is false: type ErrorParams = {limit: number} // the maximum number of allowed items ``` - `additionalProperties`: ```typescript type ErrorParams = {additionalProperty: string} // the property not defined in `properties` and `patternProperties` keywords ``` - `dependencies`: ```typescript type ErrorParams = { property: string // dependent property, missingProperty: string // required missing dependency - only the first one is reported deps: string // required dependencies, comma separated list as a string (TODO change to string[]) depsCount: number // the number of required dependencies } ``` - `format`: ```typescript type ErrorParams = {format: string} // keyword value ``` - `maximum`, `minimum`, `exclusiveMaximum`, `exclusiveMinimum`: ```typescript type ErrorParams = { limit: number // keyword value comparison: "<=" | ">=" | "<" | ">" // operation to compare the data to the limit, // with data on the left and the limit on the right } ``` - `multipleOf`: ```typescript type ErrorParams = {multipleOf: number} // keyword value ``` - `pattern`: ```typescript type ErrorParams = {pattern: string} // keyword value ``` - `required`: ```typescript type ErrorParams = {missingProperty: string} // required property that is missing ``` - `propertyNames`: ```typescript type ErrorParams = {propertyName: string} // invalid property name ``` User-defined keywords can define other keyword parameters. ### Errors i18n You can use [ajv-i18n](https://github.com/ajv-validator/ajv-i18n) package to generate errors in other languages. ### Error logging A logger instance can be passed via `logger` option to Ajv constructor. The use of other logging packages is supported as long as the package or its associated wrapper exposes the required methods. If any of the required methods are missing an exception will be thrown. - **Required Methods**: `log`, `warn`, `error` ```javascript const otherLogger = new OtherLogger() const ajv = new Ajv({ logger: { log: console.log.bind(console), warn: function warn() { otherLogger.logWarn.apply(otherLogger, arguments) }, error: function error() { otherLogger.logError.apply(otherLogger, arguments) console.error.apply(console, arguments) }, }, }) ``` ##### Options This section is moved to [Initialization options](./options.md) page ================================================ FILE: docs/codegen.md ================================================ # Code generation design [[toc]] Starting from v7 Ajv uses [CodeGen module](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) that replaced [doT](https://github.com/olado/dot) templates used earlier. The motivations for this change: - doT templates were difficult to maintain and to change, particularly for the occasional contributors. - they discouraged modularity within validation keywords code and also led to implicit dependencies between different parts of code. - they had risks of remote code execution in case untrusted schemas were used, even though all identified issues were patched. - ES6 template literals that are now widely supported offer a great alternative to both ASTs and to plain string concatenation - this option was not available when Ajv started. ## Safe code generation CodeGen module defines two tagged templates that should be passed to all code generation methods and used in other tagged templates: - `_` - to create instances of private \_Code class that will not be escaped when used in code or other tagged templates. - `str` - to create code for string expressions. For example, this code: ```typescript const x = 0 // Name is a subclass of _Code that can be safely used in code - it only allows valid identifiers // gen.const creates a unique variable name with the prefix "num". const num: Name = gen.const("num", 5) gen.if( // _`...` returns the instance of _Code with safe interpolation of `num` and `x`. // if `x` was a string, it would be inserted into code as a quoted string value rather than as a code fragment, // so if `x` contained some code, it would not be executed. _`${num} > ${x}`, () => log("greater"), () => log("smaller or equal") ) function log(comparison: string): void { // msg creates a string expression with concatenation - see generated code below // type Code = _Code | Name, _Code can only be constructed with template literals const msg: Code = str`${num} is ${comparison} than ${x}` // msg is _Code instance, so it will be inserted via another template without quotes gen.code(_`console.log(${msg})`) } ``` generates this javascript code: ```javascript const num0 = 5 if (num0 > 0) { console.log(num0 + " is greater than 0") } else { console.log(num0 + " is smaller or equal than 0") } ``` `.const`, `.if` and `.code` above are methods of CodeGen class that generate code inside class instance `gen` - see [source code](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) for all available methods and [tests](https://github.com/ajv-validator/ajv/blob/master/spec/codegen.spec.ts) for other code generation examples. These methods only accept instances of private class `_Code`, other values will be rejected by Typescript compiler - the risk to pass unsafe string is mitigated on type level. If a string variable were used in `_` template literal, its value would be safely wrapped in quotes - in many cases it is quite useful, as it allows to inject values that can be either string or number via the same template. In the worst case, the generated code could be invalid, but it will prevent the risk of code execution that attacker could pass via untrusted schema as a string value that should be inserted in code (e.g., instead of a number). Also see the comment in the example. ## Code optimization CodeGen class generates code trees and performs several optimizations before the code is rendered: 1. removes empty and unreachable branches (e.g. `else` branch after `if(true)`, etc.). 2. removes unused variable declarations. 3. replaces variables that are used only once and assigned expressions that are explicitly marked as "constant" (i.e. having referential transparency) with the expressions themselves. ::: warning Optimizations assume no side effects These optimizations assume that the expressions in `if` conditions, `for` statement headers and assigned expressions are free of any side effects - this is the case for all pre-defined validation keywords. ::: See [these tests](https://github.com/ajv-validator/ajv/blob/master/spec/codegen.spec.ts) for examples. By default Ajv does 1-pass optimization - based on the test suite it reduces the code size by 10.5% and the number of tree nodes by 16.7% (TODO benchmark the validation time). The second optimization pass changes it by less than 0.1%, so you won't need it unless you have really complex schemas or if you generate standalone code and want it to pass relevant eslint rules. Optimization mode can be changed with [options](./api.md#options): - `{code: {optimize: false}}` - to disable (e.g., when schema compilation time is more important), - `{code: {optimize: 2}}` - 2-pass optimization. ## User-defined keywords While tagged template literals wrap passed strings based on their run-time values, CodeGen class methods rely on types to ensure safety of passed parameters - there is no run-time checks that the passed value is an instance of \_Code class. It is strongly recommended to define additional keywords only with Typescript - using plain JavaScript would still allow passing unsafe strings to code generation methods. ::: warning Optimization and side-effects If your user-defined keywords need to have side-effects that are removed by optimization (see above), you may need to disable it. ::: ================================================ FILE: docs/coercion.md ================================================ # Type coercion rules To enable type coercion pass option `coerceTypes` to Ajv with `true` or `array` (it is `false` by default). See [example](./guide/modifying-data.md#coercing-data-types). The coercion rules are different from JavaScript: - to validate user input as expected - to have the coercion reversible - to correctly validate cases where different types are required in subschemas (e.g., in `anyOf`). Type coercion only happens if there is `type` keyword and if without coercion the validation would have failed. If coercion to the required type succeeds then the validation continues to other keywords, otherwise the validation fails. If there are multiple types allowed in `type` keyword the coercion will only happen if none of the types match the data and some of the scalar types are present (coercion to/from `object`/`array` is not possible). In this case the validating function will try coercing the data to each type in order until some of them succeeds. Application of these rules can have some unexpected consequences. Ajv may coerce the same value multiple times (this is why coercion reversibility is required) as needed at different points in the schema. This is particularly evident when using `oneOf`, which must test all of the subschemas. Ajv will coerce the type for each subschema, possibly resulting in unexpected failure if it can coerce to match more than one of the subschemas. Even if it succeeds, Ajv will not backtrack, so you'll get the type of the final coercion even if that's not the one that allowed the data to pass validation. If possible, structure your schema with `anyOf`, which won't validate subsequent subschemas as soon as it encounters one subschema that matches. Possible type coercions: | from type →
to type ↓ | string | number | boolean | null | array\* | | ------------------------------------------------------ | :-----------------------------------------------------------------------------: | :-----------------------------------------------: | :--------------------------------------------: | :------------------: | :--------------------------------------------: | | string | - | `x`→`""+x` | `false`→`"false"`
`true`→`"true"` | `null`→`""` | `[x]`→`x` | | number /
integer | Valid number /
integer: `x`→`+x`
| - | `false`→`0`
`true`→`1` | `null`→`0` | `[x]`→`x` | | boolean | `"false"`→`false`
`"true"`→`true`
`"abc"`⇸
`""`⇸ | `0`→`false`
`1`→`true`
`x`⇸ | - | `null`→`false` | `[false]`→`false`
`[true]`→`true` | | null | `""`→`null`
`"null"`⇸
`"abc"`⇸ | `0`→`null`
`x`⇸ | `false`→`null`
`true`⇸ | - | `[null]`→`null` | | array\* | `x`→`[x]` | `x`→`[x]` | `false`→`[false]`
`true`→`[true]` | `null`→`[null]` | - | \* Requires option `{coerceTypes: "array"}` ## Coercion from string values #### To number type Coercion to `number` is possible if the string is a valid number, `+data` is used. #### To integer type Coercion to `integer` is possible if the string is a valid number without fractional part (`data % 1 === 0`). #### To boolean type Unlike JavaScript, only these strings can be coerced to `boolean`: - `"true"` -> `true` - `"false"` -> `false` #### To null type Empty string is coerced to `null`, other strings can't be coerced. ## Coercion from number values #### To string type Always possible, `'' + data` is used #### To boolean type Unlike JavaScript, only these numbers can be coerced to `boolean`: - `1` -> `true` - `0` -> `false` #### To null type `0` coerces to `null`, other numbers can't be coerced. ## Coercion from boolean values #### To string type - `true` -> `"true"` - `false` -> `"false"` #### To number/integer types - `true` -> `1` - `false` -> `0` #### To null type `false` coerces to `null`, `true` can't be coerced. ## Coercion from null #### To string type `null` coerces to the empty string. #### To number/integer types `null` coerces to `0` #### To boolean type `null` coerces to `false` ## Coercion to and from array These coercions require that the option `coerceTypes` is `"array"`. If a scalar data is present and array is required, Ajv wraps scalar data in an array. If an array with one item is present and a scalar is required, Ajv coerces array into its item. - `"foo"` -> `[ "foo" ]` - `[ "foo" ]` -> `"foo"` ================================================ FILE: docs/components.md ================================================ # Code components [[toc]] ## Ajv classes [lib/core.ts](https://github.com/ajv-validator/ajv/blob/master/lib/core.ts) - core Ajv class without any keywords. All Ajv methods for managing schemas and extensions are defined in this class. [lib/ajv.ts](https://github.com/ajv-validator/ajv/blob/master/lib/ajv.ts) - subclass of Ajv core with JSON Schema draft-07 keywords. [lib/2019.ts](https://github.com/ajv-validator/ajv/blob/master/lib/2019.ts) - subclass of Ajv core with JSON Schema draft-2019-09 keywords. [lib/jtd.ts](https://github.com/ajv-validator/ajv/blob/master/lib/jtd.ts) - subclass of Ajv core with JSON Type Definition support. ## Schema compilation [lib/compile](https://github.com/ajv-validator/ajv/blob/master/lib/compile) - code for schema compilation [lib/compile/index.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/index.ts) - the main recursive function code for schema compilation, functions for reference resolution, the interface for schema compilation context (`SchemaCxt`). [lib/compile/context.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/context.ts) - the class for keyword code generation `KeywordCxt`. All pre-defined keywords and user-defined keywords that use `code` function are passed an instance of this class. [lib/compile/rules.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/rules.ts) - data structure to store references to all all keyword definitions that were added to Ajv instance, organised by data type. [lib/compile/subschema.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/subschema.ts) - creates schema context (`SchemaCxt`) to generate code for subschemas - used by all applicator keywords in [lib/vocabularies/applicator](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies/applicator). [lib/compile/codegen](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen) - the api for [code generation](./codegen.md). [lib/compile/validate](https://github.com/ajv-validator/ajv/blob/master/lib/compile/validate) - code to iterate the schema to generate code of validation function. ## Other components [lib/standalone](https://github.com/ajv-validator/ajv/blob/master/lib/standalone) - module to generate [standalone validation code](./standalone.md). [lib/vocabularies](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies) - pre-defined validation keywords. [lib/refs](https://github.com/ajv-validator/ajv/blob/master/lib/refs) - JSON Schema meta-schemas. ================================================ FILE: docs/faq.md ================================================ # Frequently Asked Questions The purpose of this document is to help find answers quicker. I am happy to continue the discussion about these issues, so please comment on some of the issues mentioned below or create a new issue if it seems more appropriate. [[toc]] ## Using JSON schema Ajv implements JSON schema specification. Before submitting the issue about the behaviour of any validation keywords please review them in: - [JSON Schema specification](https://tools.ietf.org/html/draft-handrews-json-schema-validation-00) (draft-07) - [JSON Schema reference](./json-schema.md) in Ajv documentation - [JSON Schema tutorial](https://spacetelescope.github.io/understanding-json-schema/) (for draft-04) #### Why Ajv validates empty object as valid? "properties" keyword does not require the presence of any properties, you need to use "required" keyword. It also doesn't require that the data is an object, so any other type of data will also be valid. To require a specific type use "type" keyword. [Strict types mode](./strict-mode.md#strict-types) introduced in v7 requires presence of "type" when "properties" are used, so the mistakes are less likely. #### Why Ajv validates only the first item of the array? "items" keyword support [two syntaxes](./json-schema.md#items) - 1) when the schema applies to all items; 2) when there is a different schema for each item in the beginning of the array. This problem means you are using the second syntax. In v7 with option `strictTuples` (`"log"` by default) this problem is less likely to happen, as Ajv would log warning about missing "minItems" and other keywords that are required to constrain tuple size. ## Ajv API for returning validation errors See [#65](https://github.com/ajv-validator/ajv/issues/65), [#212](https://github.com/ajv-validator/ajv/issues/212), [#236](https://github.com/ajv-validator/ajv/issues/236), [#242](https://github.com/ajv-validator/ajv/issues/242), [#256](https://github.com/ajv-validator/ajv/issues/256). #### Why Ajv assigns errors as a property of validation function (or instance) instead of returning an object with validation results and errors? The reasons are history (other fast validators with the same api) and performance (returning boolean is faster). Although more code is written to process errors than to handle successful results, almost all server-side validations pass. The existing API is more efficient from the performance point of view. Ajv also supports asynchronous validation (with asynchronous formats and keywords) that returns a promise that either resolves to `true` or rejects with an error. #### Would errors get overwritten in case of "concurrent" validations? No. There is no parallel execution in JavaScript, and the cooperative concurrency model of javascript makes this pattern safe. While a validation is run, no other JavaScript code that can access the same memory can be executed. As long as the errors are used in the same execution block, the errors will not be overwritten. #### Can we change / extend API to add a method that would return errors (rather than assign them to `errors` property)? No. In many cases there is a module responsible for the validation in the application, usually to load schemas and to process errors. This module is the right place to introduce any user-defined API. Convenience is a subjective thing, changing or extending API purely because of convenience would either break backward compatibility (even if it's done in a new major version it still complicates migration) or bloat API (making it more difficult to maintain). #### Why don't `"additionalProperties": false` errors display the property name? Doing this would create a precedent where validated data is used in error messages, creating a vulnerability (e.g., when ajv is used to validate API data/parameters and error messages are logged). Since the property name is already in the params object, in an application you can modify the messages in any way you need. ajv-errors package allows modifying messages as well. ## Additional properties inside compound keywords anyOf, oneOf, etc. See [#127](https://github.com/ajv-validator/ajv/issues/127), [#129](https://github.com/ajv-validator/ajv/issues/129), [#134](https://github.com/ajv-validator/ajv/issues/134), [#140](https://github.com/ajv-validator/ajv/issues/140), [#193](https://github.com/ajv-validator/ajv/issues/193), [#205](https://github.com/ajv-validator/ajv/issues/205), [#238](https://github.com/ajv-validator/ajv/issues/238), [#264](https://github.com/ajv-validator/ajv/issues/264). #### Why the keyword `additionalProperties: false` fails validation when some properties are "declared" inside a subschema in `anyOf`/etc.? The keyword `additionalProperties` creates the restriction on validated data based on its own value (`false` or schema object) and on the keywords `properties` and `patternProperties` in the SAME schema object. JSON Schema validators must NOT take into account properties used in other schema objects. While you can expect that the schema below would allow the objects either with properties `foo` and `bar` or with properties `foo` and `baz` and all other properties will be prohibited, this schema will only allow objects with one property `foo` (an empty object and any non-objects will also be valid): ```javascript { type: "object", properties: {foo: {type: "number"}}, additionalProperties: false, oneOf: [ {properties: {bar: {type: "number"}}}, {properties: {baz: {type: "number"}}} ] } ``` The reason for that is that `additionalProperties` keyword ignores properties inside `oneOf` keyword subschemas. That's not the limitation of Ajv or any other validator, that's how it must work according to the standard. There are several ways to implement the described logic that would allow two properties, please see the suggestions in the issues mentioned above. #### Why the validation fails when I use option `removeAdditional` with the keyword `anyOf`/etc.? This problem is related to the problem explained above - properties treated as additional in the sense of `additionalProperties` keyword, based on `properties`/`patternProperties` keyword in the same schema object. See the example in the [Removing Additional Data](https://ajv.js.org/guide/modifying-data.html#removing-additional-properties) section of the docs. ## Generating schemas with resolved references ($ref) See [#22](https://github.com/ajv-validator/ajv/issues/22), [#125](https://github.com/ajv-validator/ajv/issues/125), [#146](https://github.com/ajv-validator/ajv/issues/146), [#228](https://github.com/ajv-validator/ajv/issues/228), [#336](https://github.com/ajv-validator/ajv/issues/336), [#454](https://github.com/ajv-validator/ajv/issues/454). #### Why Ajv does not replace references ($ref) with the actual referenced schemas as some validators do? 1. The scope of Ajv is validating data against JSON Schemas; inlining referenced schemas is not necessary for validation. When Ajv generates code for validation it either inlines the code of referenced schema or uses function calls. Doing schema manipulation is more complex and out of scope. 2. When schemas are recursive (or mutually recursive) resolving references would result in self-referencing recursive data-structures that can be difficult to process. 3. There are cases when such inlining would also require adding (or modifying) `id` attribute in the inlined schema fragment to make the resulting schema equivalent. There were many conversations about the meaning of `$ref` in [JSON Schema GitHub organisation](https://github.com/json-schema-org). The consensus is that while it is possible to treat `$ref` as schema inclusion with two caveats (above), this interpretation is unnecessary complex. A more efficient approach is to treat `$ref` as a delegation, i.e. a special keyword that validates the current data instance against the referenced schema. The analogy with programming languages is that `$ref` is a function call rather than a macro. See [here](https://github.com/json-schema-org/json-schema-spec/issues/279), for example. #### How can I generate a schema where `$ref` keywords are replaced with referenced schemas? There are two possible approaches: 1. Traverse schema (e.g. with json-schema-traverse) and replace every `$ref` with the referenced schema. 2. Use a specially constructed JSON Schema with a [user-defined keyword](./keywords.md) to traverse and modify your schema. ================================================ FILE: docs/guide/async-validation.md ================================================ # Asynchronous validation You can define formats and keywords that perform validation asynchronously by accessing database or some other service. You should add `async: true` in the keyword or format definition (see [addFormat](./api.md#api-addformat), [addKeyword](./api.md#api-addkeyword) and [User-defined keywords](./keywords.md)). If your schema uses asynchronous formats/keywords or refers to some schema that contains them it should have `"$async": true` keyword so that Ajv can compile it correctly. If asynchronous format/keyword or reference to asynchronous schema is used in the schema without `$async` keyword Ajv will throw an exception during schema compilation. ::: warning Use $async: true in referenced schemas All asynchronous subschemas that are referenced from the current or other schemas should have `"$async": true` keyword as well, otherwise the schema compilation will fail. ::: Validation function for an asynchronous format/keyword should return a promise that resolves with `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return errors from the keyword function). Ajv compiles asynchronous schemas to [async functions](http://tc39.github.io/ecmascript-asyncawait/). Async functions are supported in Node.js 7+ and all modern browsers. You can supply a transpiler as a function via `processCode` option. See [Options](./api.md#options). The compiled validation function has `$async: true` property (if the schema is asynchronous), so you can differentiate these functions if you are using both synchronous and asynchronous schemas. Validation result will be a promise that resolves with validated data or rejects with an exception `Ajv.ValidationError` that contains the array of validation errors in `errors` property. Example: ```javascript const ajv = new Ajv() ajv.addKeyword({ keyword: "idExists", async: true, type: "number", validate: checkIdExists, }) async function checkIdExists(schema, data) { // this is just an example, you would want to avoid SQL injection in your code const rows = await sql(`SELECT id FROM ${schema.table} WHERE id = ${data}`) return !!rows.length // true if record is found } const schema = { $async: true, properties: { userId: { type: "integer", idExists: {table: "users"}, }, postId: { type: "integer", idExists: {table: "posts"}, }, }, } const validate = ajv.compile(schema) validate({userId: 1, postId: 19}) .then(function (data) { console.log("Data is valid", data) // { userId: 1, postId: 19 } }) .catch(function (err) { if (!(err instanceof Ajv.ValidationError)) throw err // data is invalid console.log("Validation errors:", err.errors) }) ``` ### Using transpilers ```javascript const ajv = new Ajv({processCode: transpileFunc}) const validate = ajv.compile(schema) // transpiled es7 async function validate(data).then(successFunc).catch(errorFunc) ``` See [Options](../options). ================================================ FILE: docs/guide/combining-schemas.md ================================================ # Combining schemas [[toc]] ## Combining schemas with $ref You can structure your validation logic across multiple schema files and have schemas reference each other using `$ref` keyword. Example: ```javascript const schema = { $id: "http://example.com/schemas/schema.json", type: "object", properties: { foo: {$ref: "defs.json#/definitions/int"}, bar: {$ref: "defs.json#/definitions/str"}, }, } const defsSchema = { $id: "http://example.com/schemas/defs.json", definitions: { int: {type: "integer"}, str: {type: "string"}, }, } ``` Now to compile your schema you can either pass all schemas to Ajv instance: ```javascript const ajv = new Ajv({schemas: [schema, defsSchema]}) const validate = ajv.getSchema("http://example.com/schemas/schema.json") ``` or use `addSchema` method: ```javascript const ajv = new Ajv() const validate = ajv.addSchema(defsSchema).compile(schema) ``` See [Options](../options.md) and [addSchema](../api.md#add-schema) method. ::: tip Reference resolution - `$ref` is resolved as the uri-reference using schema \$id as the base URI (see the example). - References can be recursive (and mutually recursive) to implement the schemas for different data structures (such as linked lists, trees, graphs, etc.). - You don't have to host your schema files at the URIs that you use as schema \$id. These URIs are only used to identify the schemas, and according to JSON Schema specification validators should not expect to be able to download the schemas from these URIs. - The actual location of the schema file in the file system is not used. - You can pass the identifier of the schema as the second parameter of `addSchema` method or as a property name in `schemas` option. This identifier can be used instead of (or in addition to) schema \$id. - You cannot have the same \$id (or the schema identifier) used for more than one schema - the exception will be thrown. - You can implement dynamic resolution of the referenced schemas using `compileAsync` method. In this way you can store schemas in any system (files, web, database, etc.) and reference them without explicitly adding to Ajv instance. See [Asynchronous schema compilation](./managing-schemas.md#asynchronous-schema-compilation). ::: ## Extending recursive schemas While statically defined `$ref` keyword allows to split schemas to multiple files, it is difficult to extend recursive schemas - the recursive reference(s) in the original schema points to the original schema, and not to the extended one. So in JSON Schema draft-07 the only available solution to extend the recursive schema was to redefine all sections of the original schema that have recursion. It was particularly repetitive when extending meta-schema, as it has many recursive references, but even in a schema with a single recursive reference extending it was very verbose. JSON Schema draft-2019-09 and the upcoming draft defined the mechanism for dynamic recursion using keywords `$recursiveRef`/`$recursiveAnchor` (draft-2019-09) or `$dynamicRef`/`$dynamicAnchor` (the next JSON Schema draft) that is somewhat similar to "open recursion" in functional programming. Consider this recursive schema with static recursion: ```javascript const treeSchema = { $id: "https://example.com/tree", type: "object", required: ["data"], properties: { data: true, children: { type: "array", items: {$ref: "#"}, }, }, } ``` The only way to extend this schema to prohibit additional properties is by adding `additionalProperties` keyword right in the schema - this approach can be impossible if you do not control the source of the original schema. Ajv also provided the additional keywords in [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) package to extend schemas by treating them as plain JSON data. While this approach may work for you, it is non-standard and therefore not portable. The new keywords for dynamic recursive references allow extending this schema without modifying it: ```javascript const treeSchema = { $id: "https://example.com/tree", $recursiveAnchor: true, type: "object", required: ["data"], properties: { data: true, children: { type: "array", items: {$recursiveRef: "#"}, }, }, } const strictTreeSchema = { $id: "https://example.com/strict-tree", $recursiveAnchor: true, $ref: "tree", unevaluatedProperties: false, } import Ajv2019 from "ajv/dist/2019" // const Ajv2019 = require("ajv/dist/2019") const ajv = new Ajv2019({ schemas: [treeSchema, strictTreeSchema], }) const validate = ajv.getSchema("https://example.com/strict-tree") ``` See [dynamic-refs](https://github.com/ajv-validator/ajv/blob/master/spec/dynamic-ref.spec.ts) test for the example using `$dynamicAnchor`/`$dynamicRef`. At the moment Ajv implements the spec for dynamic recursive references with these limitations: - `$recursiveAnchor`/`$dynamicAnchor` can only be used in the schema root. - `$recursiveRef`/`$dynamicRef` can only be hash fragments, without URI. Ajv also does not support dynamic references in [asynchronous schemas](#asynchronous-validation) (Ajv extension) - it is assumed that the referenced schema is synchronous, and there is no validation-time check for it. ## $data reference With `$data` option you can use values from the validated data as the values for the schema keywords. See [proposal](https://github.com/json-schema-org/json-schema-spec/issues/51) for more information about how it works. `$data` reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems. The value of "$data" should be a [JSON-pointer](https://datatracker.ietf.org/doc/rfc6901/) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a [relative JSON-pointer](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00) (it is relative to the current point in data; if the \$data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema). Examples. This schema requires that the value in property `smaller` is less or equal than the value in the property larger: ```javascript const ajv = new Ajv({$data: true}) const schema = { properties: { smaller: { type: "number", maximum: {$data: "1/larger"}, }, larger: {type: "number"}, }, } const validData = { smaller: 5, larger: 7, } ajv.validate(schema, validData) // true ``` This schema requires that the properties have the same format as their field names: ```javascript const schema = { additionalProperties: { type: "string", format: {$data: "0#"}, }, } const validData = { "date-time": "1963-06-19T08:30:06.283185Z", email: "joe.bloggs@example.com", } ``` `$data` reference is resolved safely - it won't throw even if some property is undefined. If `$data` resolves to `undefined` the validation succeeds (with the exclusion of `const` keyword). If `$data` resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails. ## $merge and $patch keywords With the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON Schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://datatracker.ietf.org/doc/rfc7396/) and [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/rfc6902/). To add keywords `$merge` and `$patch` to Ajv instance use this code: ```javascript require("ajv-merge-patch")(ajv) ``` Examples. Using `$merge`: ```javascript { $merge: { source: { type: "object", properties: {p: {type: "string"}}, additionalProperties: false }, with: { properties: {q: {type: "number"}} } } } ``` Using `$patch`: ```javascript { $patch: { source: { type: "object", properties: {p: {type: "string"}}, additionalProperties: false }, with: [{op: "add", path: "/properties/q", value: {type: "number"}}] } } ``` The schemas above are equivalent to this schema: ```javascript { type: "object", properties: { p: {type: "string"}, q: {type: "number"} }, additionalProperties: false } ``` The properties `source` and `with` in the keywords `$merge` and `$patch` can use absolute or relative `$ref` to point to other schemas previously added to the Ajv instance or to the fragments of the current schema. See the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) for more information. ================================================ FILE: docs/guide/environments.md ================================================ # Execution environments [[toc]] ## Server-side Node.js The main consideration for using Ajv server-side is to [manage compiled schemas](./managing-schemas) correctly, ensuring that the same schema is not compiled more than once. ## Short-lived environments Depending on the life-time of the environments, the benefits from "compile once - validate many times" model can be limited - you can consider using [standalone validation code](../standalone). If you have a pre-defined set of schemas, you can: 1. compile all schemas in the build step - you can either write your own script or use [ajv-cli](https://github.com/ajv-validator/ajv). 2. generate and beautify standalone validation code - you can have all your schemas exported from one file. 3. additionally, you can inline all dependencies on Ajv or ajv-formats using any bundling tools. 4. deploy compiled schemas as part of your application or library (with or without dependency on Ajv, depending on whether you did step 3 and which validation keywords are used in the schemas) Please see [gajus/table](https://github.com/gajus/table) package that pre-compiles schemas in this way. Even if your schemas need to be stored in the database, you can still compile schemas once and store your validation functions alongside schemas in the database as well, loading them on demand. ## Browsers See [Content Security Policy](../security.md#content-security-policy) to decide how best to use Ajv in the browser for your use case. Whether you compile schemas in the browser or use [standalone validation code](../standalone), it is recommended that you bundle them together with your application code. If you need to use Ajv in several application bundles you can create a separate UMD bundles of Ajv using `npm run bundle` script. In this case you need to load Ajv using the correct bundle, depending on which schema language and which version you need to use: ```html ``` ```html ``` ```html ``` This bundle can be used with different module systems; it creates global `ajv`/`ajv2019`/`ajvJTD` if no module system is found. The browser bundles are available on [cdnjs](https://cdnjs.com/libraries/ajv). ::: warning Some frameworks re-define require Some frameworks, e.g. Dojo, may redefine global require in a way that is not compatible with CommonJS module format. In this case Ajv bundle has to be loaded before the framework and then you can use global `ajv` (see issue [#234](https://github.com/ajv-validator/ajv/issues/234)). ::: ::: warning Internet Explorer 11 Ajv v8 in IE 11 will not work straight out of the box. To use it either [recompile it](../standalone.md), or set the options [unicodeRegExp](../options.md#unicoderegexp) to `false` and `code: { es5: true }`, and transpile the Ajv node module (see issue [#1585](https://github.com/ajv-validator/ajv/issues/1585#issuecomment-832486204)). ::: ## ES5 environments You need to: - recompile Typescript to ES5 target - it is set to 2018 in the bundled compiled code. - generate ES5 validation code: ```javascript const ajv = new Ajv({code: {es5: true}}) ``` See [Advanced options](https://github.com/ajv-validator/ajv/blob/master/docs/api.md#advanced-options). ## CJS vs ESM exports The default configuration of AJV is to generate code in ES6 with Common JS (CJS) exports. This can be changed by setting the ES Modules(ESM) flag. ```javascript const ajv = new Ajv({code: {esm: true}}) ``` ## Other JavaScript environments Ajv is used in other JavaScript environments, including Electron apps, WeChat mini-apps and many others, where the same considerations apply as above: - compilation performance - restrictive content security policy - bundle size If any of this is important, you may have better results with pre-compiled [standalone validation code](../standalone). ## Command line interface Ajv can be used from the terminal in any operating system supported by Node.js CLI is available as a separate npm package [ajv-cli](https://github.com/ajv-validator/ajv-cli). It supports: - compiling JSON Schemas to test their validity - generating [standalone validation code](./docs/standalone.md) that exports validation function(s) - migrating schemas to draft-07 and draft-2019-09 (using [json-schema-migrate](https://github.com/epoberezkin/json-schema-migrate)) - validating data file(s) against JSON Schema - testing expected validity of data against JSON Schema - referenced schemas - user-defined meta-schemas, validation keywords and formats - files in JSON, JSON5, YAML, and JavaScript format - all Ajv options - reporting changes in data after validation in [JSON-patch](https://datatracker.ietf.org/doc/rfc6902/) format ================================================ FILE: docs/guide/formats.md ================================================ # Format validation ## String formats From version 7 Ajv does not include formats defined by JSON Schema specification - these and several other formats are provided by [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin. To add all formats from this plugin: ```javascript const Ajv = require("ajv") const addFormats = require("ajv-formats") const ajv = new Ajv() addFormats(ajv) ``` ```typescript import Ajv from "ajv" import addFormats from "ajv-formats" const ajv = new Ajv() addFormats(ajv) ``` See [ajv-formats](https://github.com/ajv-validator/ajv-formats) documentation for further details. It is recommended NOT to use "format" keyword implementations with untrusted data, as they may use potentially unsafe regular expressions (even though known issues are fixed) - see [ReDoS attack](../security.md#redos-attack). ::: danger Format validation of untrusted data If you need to use "format" keyword to validate untrusted data, you MUST assess their suitability and safety for your validation scenarios. ::: The following formats are defined in [ajv-formats](https://github.com/ajv-validator/ajv-formats) for string validation with "format" keyword: - _date_: full-date according to [RFC3339](http://tools.ietf.org/html/rfc3339#section-5.6). - _time_: time with optional time-zone. - _date-time_: date-time from the same source (time-zone is mandatory). - _duration_: duration from [RFC3339](https://tools.ietf.org/html/rfc3339#appendix-A) - _uri_: full URI. - _uri-reference_: URI reference, including full and relative URIs. - _uri-template_: URI template according to [RFC6570](https://datatracker.ietf.org/doc/rfc6570/) - _url_ (deprecated): [URL record](https://url.spec.whatwg.org/#concept-url). - _email_: email address. - _hostname_: host name according to [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5). - _ipv4_: IP address v4. - _ipv6_: IP address v6. - _regex_: tests whether a string is a valid regular expression by passing it to RegExp constructor. - _uuid_: Universally Unique Identifier according to [RFC4122](https://datatracker.ietf.org/doc/rfc4122/). - _json-pointer_: JSON-pointer according to [RFC6901](https://datatracker.ietf.org/doc/rfc6901/). - _relative-json-pointer_: relative JSON-pointer according to [this draft](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00). ::: warning Additional formats in ajv-formats-draft2019 JSON Schema draft-07 also defines formats `iri`, `iri-reference`, `idn-hostname` and `idn-email` for URLs, hostnames and emails with international characters. These formats are available in [ajv-formats-draft2019](https://github.com/luzlab/ajv-formats-draft2019) plugin. ::: ## User-defined formats You can add and replace any formats using [addFormat](../api.md#ajv-addformat-name-string-format-format-ajv) method: ```javascript ajv.addFormat("identifier", /^a-z\$_[a-zA-Z$_0-9]*$/) ``` Ajv also allows defining the formats that would be applied to numbers only: ```javascript ajv.addFormat("byte", { type: "number", validate: (x) => x >= 0 && x <= 255 && x % 1 == 0, }) ``` ## Formats and standalone validation code If you use formats from [ajv-formats](https://github.com/ajv-validator/ajv-formats) package, [standalone validation code](../standalone) will be supported out of the box. ::: warning Standalone code and Ajv versions You need to make sure that ajv-formats imports the same version and the same code of ajv as the one you use in your application for standalone validation code to work (because of `instanceof` check that is currently used). `npm` and other package managers may not update the version of ajv dependency of ajv-formats when you update version of ajv in your application - the workaround is to use clean npm installation. ::: If you define your own formats, for standalone code generation to work you need to pass the code snippet that evaluates to an object with all defined formats to the option `code.formats`: ```javascript const {default: Ajv, _} = require("ajv") const ajv = new Ajv({code: {formats: _`require("./my_formats")`}}) ``` ```typescript import Ajv, {_} from "ajv" const ajv = new Ajv({code: {formats: _`require("./my_formats")`}}) ``` ================================================ FILE: docs/guide/getting-started.md ================================================ # Getting started [[toc]] ## Install ::: tip Node REPL You can try Ajv without installing it in the Node.js REPL: [https://runkit.com/npm/ajv](https://runkit.com/npm/ajv) ::: To install Ajv version 8: ```bash npm install ajv ``` If you need to use Ajv with [JSON Schema draft-04](./schema-language#draft-04), you need to install Ajv version 6: ```bash npm install ajv@6 ``` See [Contributing](../CONTRIBUTING.md) on how to run the tests locally ## Basic data validation Ajv takes a schema for your JSON data and converts it into a very efficient JavaScript code that validates your data according to the schema. To create a schema you can use either [JSON Schema](../json-schema) or [JSON Type Definition](../json-type-definition) - check out [Choosing schema language](./schema-language), they have different advantages and disadvantages. For example, to validate an object that has a required property "foo" (an integer number), an optional property "bar" (a string) and no other properties: ```javascript const Ajv = require("ajv") const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} const schema = { type: "object", properties: { foo: {type: "integer"}, bar: {type: "string"} }, required: ["foo"], additionalProperties: false } const validate = ajv.compile(schema) const data = { foo: 1, bar: "abc" } const valid = validate(data) if (!valid) console.log(validate.errors) ``` ```javascript const Ajv = require("ajv/dist/jtd") const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} const schema = { properties: { foo: {type: "int32"} }, optionalProperties: { bar: {type: "string"} } } const validate = ajv.compile(schema) const data = { foo: 1, bar: "abc" } const valid = validate(data) if (!valid) console.log(validate.errors) ``` Ajv compiles schemas to functions and caches them in all cases (using the schema itself as a key in a Map), so that the next time the same schema object is used it won't be compiled again. ::: tip Best performance: compile and getSchema methods The best performance is achieved when using compiled functions returned by `compile` or `getSchema` methods. While execution of the compiled validation function is very fast, its compilation is relatively slow, so you need to make sure that you compile schemas only once and re-use compiled validation functions. See [Managing multiple schemas](./managing-schemas). ::: ::: warning Save errors property Every time a validation function (or `ajv.validate`) is called the `errors` property is overwritten. You need to copy the `errors` array reference to another variable if you want to use it later (e.g. in the callback). See [Validation errors](../api.md#validation-errors). ::: ## Parsing and serializing JSON Ajv can compile efficient parsers and serializers from [JSON Type Definition](../json-type-definition) schemas. Serializing the data with a function specialized to your data shape can be more than 10x compared with `JSON.stringify`. Parsing the data replaces the need for separate validation after generic parsing with `JSON.parse` (although validation itself is usually much faster than parsing). In case your JSON string is valid, the specialized parsing is approximately as fast as JSON.parse, but in case your JSON is invalid, the specialized parsing would fail much faster - so it can be very efficient in some scenarios. For the same data structure, you can compile parser and serializer in this way: ```javascript const Ajv = require("ajv/dist/jtd") const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} const schema = { properties: { foo: {type: "int32"} }, optionalProperties: { bar: {type: "string"} } } const serialize = ajv.compileSerializer(schema) const data = { foo: 1, bar: "abc" } console.log(serialize(data)) const parse = ajv.compileParser(schema) const json = '{"foo": 1, "bar": "abc"}' const invalidJson = '{"unknown": "abc"}' parseAndLog(json) // logs {foo: 1, bar: "abc"} parseAndLog(invalidJson) // logs error and position function parseAndLog(json) { const data = parse(json) if (data === undefined) { console.log(parse.message) // error message from the last parse call console.log(parse.position) // error position in string } else { console.log(data) } } ``` ::: tip Lower parsing performance of empty schemas You would have smaller performance benefits in case your schema contains some properties or other parts that are empty schemas (`{}`) - parser would call `JSON.parse` in this case. ::: ::: warning JTD discriminator schema The performance of parsing discriminator schemas depends on the position of discriminator tag in the schema - the best parsing performance will be achieved if the tag is the first property - this is how compiled JTD serializers generate JSON in case of discriminator schemas. Also, if discriminator tag were to be repeated in JSON, the second value would be ignored and the object still validated according to the first tag. ::: ::: warning Compiled parsers do NOT throw exceptions Compiled parsers, unlike JSON.parse, do not throw the exception in case JSON string is not a valid JSON or in case data is invalid according to the schema. As soon as the parser determines that either JSON or data is invalid, it returns `undefined` and reports error and position via parsers properties `message` and `position`. ::: ================================================ FILE: docs/guide/managing-schemas.md ================================================ # Managing schemas [[toc]] ## Re-using validation functions Ajv validation model is optimized for server side execution, when schema compilation happens only once and validation happens multiple times - this has a substantial performance benefit comparing with validators that interpret the schema in the process of validation. Transition from template-based code generation in Ajv v6 to the tree-based in v7 brought: - type-level safety against code injection via untrusted schemas - more efficient validation code (via [tree optimizations](../codegen.md#code-optimization)) - smaller memory footprint of compiled functions (schemas are no longer serialized) - smaller bundle size - more maintainable code These improvements cost slower schema compilation, and increased chance of re-compilation in case you pass a different schema object (see [#1413](https://github.com/ajv-validator/ajv/issues/1413)), so it is very important to manage schemas correctly, so they are only compiled once. There are several approaches to manage compiled schemas. ## Standalone validation code The motivation to pre-compile schemas: - faster startup times - lower memory footprint/bundle size - compatible with strict content security policies - almost no risk to compile schema more than once - better for short-lived environments See [Standalone validation code](../standalone) for the details. There are scenarios when it can be not possible or difficult: - dynamic or user-provided schemas - while you can do caching, it can be either difficult to implement or inefficient. - user-defined keywords that use closures that are difficult to serialize as code. ## Compiling during initialization The simplest approach is to compile all your schemas when the application starts, outside of the code that handles requests. It can be done simply in the module scope: ```javascript const Ajv = require("ajv").default const schema_user = require("./schema_user.json") const ajv = new Ajv() const validate_user = ajv.compile(schema_user) // this is just some abstract API framework app.post("/user", async (cxt) => { if (validate_user(cxt.body)) { // create user } else { // report error cxt.status(400) } }) ``` ```javascript import Ajv from "ajv" import * as schema_user from "./schema_user.json" const ajv = new Ajv() const validate_user = ajv.compile(schema_user) interface User { username: string } // this is just some abstract API framework app.post("/user", async (cxt) => { if (validate_user(cxt.body)) { // create user } else { // report error cxt.status(400) } }) ``` ::: warning Use single Ajv instance It is recommended to use a single Ajv instance for the whole application, so if you use validation in more than one module, you should: - require Ajv in a separate module responsible for validation - compile all validators there - export them to be used in multiple modules of your application ::: ## Using Ajv instance cache Another, more effective approach, is to use Ajv instance cache to have all compiled validators available anywhere in your application from a single import. In this case you would have a separate module where you instantiate Ajv and use this instance in your application. You can load all schemas and add them to Ajv instance in a single `validation` module: ```javascript const Ajv = require("ajv") const schema_user = require("./schema_user.json") const schema_document = require("./schema_document.json") const ajv = exports.ajv = new Ajv() ajv.addSchema(schema_user, "user") ajv.addSchema(schema_document, "document") ``` ```typescript import Ajv from "ajv" import * as schema_user from "./schema_user.json" import * as schema_document from "./schema_document.json" export const ajv = new Ajv() ajv.addSchema(schema_user, "user") ajv.addSchema(schema_document, "document") ``` And then you can import Ajv instance and access any schema in any application module, for example `user` module: ```javascript const {ajv} = require("./validation") // this is just some abstract API framework app.post("/user", async (cxt) => { const validate = ajv.getSchema("user") if (validate(cxt.body)) { // create user } else { // report error cxt.status(400) } }) ``` ```javascript import ajv from "./validation" interface User { username: string } // this is just some abstract API framework app.post("/user", async (cxt) => { const validate = ajv.getSchema("user") if (validate(cxt.body)) { // create user } else { // report error cxt.status(400) } }) ``` ::: tip On-demand vs preliminary compilation In the example above, schema compilation happens only once, on the first API call, not at the application start-up time. It means that the application would start a bit faster, but the first API call would be a bit slower. If this is undesirable, you could, for example, call `getSchema` for all added schemas after they are added, then when `getSchema` is called inside route handler it would simply get compiled validation function from the instance cache. ::: ### Cache key: schema vs key vs $id In the example above, the key passed to the `addSchema` method was used to retrieve schemas from the cache. Other options are: - use schema root $id attribute. While it usually looks like URI, it does not mean Ajv downloads it from this URI - this is simply $id used to identify and access the schema. You can though configure Ajv to download schemas on demand - see [Asynchronous schema loading](#asynchronous-schema-loading) - use schema object itself as a key to the cache (it is possible, because Ajv uses Map). This approach is not recommended, because it would only work if you pass the same instance of the schema object that was passed to `addSchema` method - it is easy to make a mistake that would result in schema being compiled every time it is used. ### Pre-adding all schemas vs adding on demand In the example above all schemas were added in advance. It is also possible, to add schemas as they are used - it can be helpful if there are many schemas. In this case, you need to check first whether the schema is already added by calling `getSchema` method - it would return `undefined` if not: ```javascript const schema_user = require("./schema_user.json") let validate = ajv.getSchema("user") if (!validate) { ajv.addSchema(schema_user, "user") validate = ajv.getSchema("user") } ``` If your schema has `$id` attribute, for example: ```json { "$id": "https://example.com/user.json", "type": "object", "properties": { "username": {"type": "string"} }, required: ["username"] } ``` then the above logic can be simpler: ```javascript const schema_user = require("./schema_user.json") const validate = ajv.getSchema("https://example.com/user.json") || ajv.compile(schema_user) ``` The above is possible because when the schema has `$id` attribute `compile` method both compiles the schema (returning the validation function) and adds it to the Ajv instance cache at the same time. ### Asynchronous schema loading There are cases when you need to have a large collection of schemas stored in some database or on the remote server. In this case you are likely to use schema `$id` as some resource identifier to retrieve it - either network URI or database ID. You can use `compileAsync` [method](./api.md#api-compileAsync) to asynchronously load the schemas as they are compiled, loading the schemas that are referenced from compiled schemas on demand. Ajv itself does not do any IO operations, it uses the function you supply via `loadSchema` [option](./api.md#options) to load schema from the passed ID. This function should return `Promise` that resolves to the schema (you can use async function, as in the example). Example: ```javascript const ajv = new Ajv({loadSchema: loadSchema}) ajv.compileAsync(schema).then(function (validate) { const valid = validate(data) // ... }) async function loadSchema(uri) { const res = await request.json(uri) if (res.statusCode >= 400) throw new Error("Loading error: " + res.statusCode) return res.body } ``` ## Caching schemas in your code You can maintain cache of compiled schemas in your application independently from Ajv. It can be helpful in cases when you have multiple Ajv instances because, for example: - you need to compile different schemas with different options - you use both JSON Schema and JSON Type Definition schemas in one application - you have $id conflicts between different third party schemas you do not control Whatever approach you use, you need to ensure that each schema is compiled only once. ================================================ FILE: docs/guide/modifying-data.md ================================================ # Modifying data during validation [[toc]] ## General considerations Ajv has several options that allow to modify data during validation: - removeAdditional - to remove properties not defined in the schema object. - useDefaults - to assign defaults from the schema to the validated data properties. - coerceTypes - to change data type, when possible, to match the type(s) in the schema. You can also define keywords that modify data. ::: tip NOT possible to modify root data It is not possible to modify the root data instance passed to the validation function, only data properties can be modified. This is related to how JavaScript passes parameters, and not a limitation of Ajv. ::: ::: warning Non-portable functionality This functionality is non-standard - this is likely to be unsupported in other JSON Schema validator implementations. ::: ::: danger Unexpected results when modifying data While pure schema validation produces the results independent of the keywords and subschema order, enabling any feature that may modify the data makes validation impure and its results are likely to depend on the order of evaluation of keywords and subschemas. The order of evaluation of subschemas in keywords like `allOf` is always the same as the order of subschemas in the array. On another hand, the order of evaluation of keywords, while consistent between validations and not dependent on how schema object is created, is neither documented nor guaranteed, so it can change in the future major versions (and, in rare cases, it can change in minor version - e.g. when there is bug that needs to be fixed). It is strongly recommended to always put user-defined keywords that can mutate data in separate subschemas inside `allOf` keyword to make the order of evaluation unambiguous. The exceptions to this recommendation are pre-defined `default` and `type` keywords - they must remain in the same schema as other keywords. ::: ## Removing additional properties With [option `removeAdditional`](./api.md#options) (added by [andyscott](https://github.com/andyscott)) you can filter data during the validation. This option modifies original data. Example: ```javascript const ajv = new Ajv({removeAdditional: true}) const schema = { additionalProperties: false, properties: { foo: {type: "number"}, bar: { additionalProperties: {type: "number"}, properties: { baz: {type: "string"}, }, }, }, } const data = { foo: 0, additional1: 1, // will be removed; `additionalProperties` == false bar: { baz: "abc", additional2: 2, // will NOT be removed; `additionalProperties` != false }, } const validate = ajv.compile(schema) console.log(validate(data)) // true console.log(data) // { "foo": 0, "bar": { "baz": "abc", "additional2": 2 } ``` If `removeAdditional` option in the example above were `"all"` then both `additional1` and `additional2` properties would have been removed. If the option were `"failing"` then property `additional1` would have been removed regardless of its value and property `additional2` would have been removed only if its value were failing the schema in the inner `additionalProperties` (so in the example above it would have stayed because it passes the schema, but any non-number would have been removed). ::: warning Unexpected results when using removeAdditional with anyOf/oneOf If you use `removeAdditional` option with `additionalProperties` keyword inside `anyOf`/`oneOf` keywords your validation can fail with this schema. To make it work as you expect, you have to use discriminated union with [discriminator](../json-schema.md#discriminator) keyword (requires `discriminator` option). ::: For example, with this non-discriminated union you will have unexpected results: ```javascript { type: "object", oneOf: [ { properties: { foo: {type: "string"} }, required: ["foo"], additionalProperties: false }, { properties: { bar: {type: "integer"} }, required: ["bar"], additionalProperties: false } ] } ``` The intention of the schema above is to allow objects with either the string property "foo" or the integer property "bar", but not with both and not with any other properties. With the option `removeAdditional: true` the validation will pass for the object `{ "foo": "abc"}` but will fail for the object `{"bar": 1}`. It happens because while the first subschema in `oneOf` is validated, the property `bar` is removed because it is an additional property according to the standard (because it is not included in `properties` keyword in the same schema). While this behaviour is unexpected (issues [#129](https://github.com/ajv-validator/ajv/issues/129), [#134](https://github.com/ajv-validator/ajv/issues/134)), it is correct. To have the expected behaviour (both objects are allowed and additional properties are removed) the schema has to be refactored in this way: ```javascript { type: "object", properties: { foo: {type: "string"}, bar: {type: "integer"} }, additionalProperties: false, oneOf: [{required: ["foo"]}, {required: ["bar"]}] } ``` The schema above is also more efficient - it will compile into a faster function. For discriminated unions you could schemas with [discriminator](../json-schema.md#discriminator) keyword (it requires `discriminator: true` option): ```javascript { type: "object", discriminator: {propertyName: "tag"}, required: ["tag"], oneOf: [ { properties: { tag: {const: "foo"}, foo: {type: "string"} }, required: ["foo"], additionalProperties: false }, { properties: { tag: {const: "bar"}, bar: {type: "integer"} }, required: ["bar"], additionalProperties: false } ] } ``` With this schema, only one subschema in `oneOf` will be evaluated, so `removeAdditional` option will work as expected. See [discriminator](../json-schema.md#discriminator) keyword. ## Assigning defaults With [option `useDefaults`](./options.md#options) Ajv will assign values from `default` keyword in the schemas of `properties` and `items` (when it is the array of schemas) to the missing properties and items. With the option value `"empty"` properties and items equal to `null` or `""` (empty string) will be considered missing and assigned defaults. This option modifies original data. ::: warning Defaults are deep-cloned The default value is inserted in the generated validation code as a literal, so the value inserted in the data will be the deep clone of the default in the schema. ::: Example 1 (`default` in `properties`): ```javascript const ajv = new Ajv({useDefaults: true}) const schema = { type: "object", properties: { foo: {type: "number"}, bar: {type: "string", default: "baz"}, }, required: ["foo", "bar"], } const data = {foo: 1} const validate = ajv.compile(schema) console.log(validate(data)) // true console.log(data) // { "foo": 1, "bar": "baz" } ``` Example 2 (`default` in `items`): ```javascript const schema = { type: "array", items: [{type: "number"}, {type: "string", default: "foo"}], } const data = [1] const validate = ajv.compile(schema) console.log(validate(data)) // true console.log(data) // [ 1, "foo" ] ``` With `useDefaults` option `default` keywords throws exception during schema compilation when used in: - not in `properties` or `items` subschemas - in schemas inside `anyOf`, `oneOf` and `not` (see [#42](https://github.com/ajv-validator/ajv/issues/42)) - in `if` schema - in schemas generated by user-defined _macro_ keywords The strict mode option can change the behaviour for these unsupported defaults (`strict: false` to ignore them, `"log"` to log a warning). See [Strict mode](../strict-mode.md). ::: tip Default with discriminator keyword Defaults will be assigned in schemas inside `oneOf` in case [discriminator](../json-schema.md#discriminator) keyword is used. ::: ## Coercing data types When you are validating user inputs all your data properties are usually strings. The option `coerceTypes` allows you to have your data types coerced to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards. This option modifies original data. ::: warning Type coercion with scalar values If you pass a scalar value to the validating function its type will be coerced and it will pass the validation, but the value of the variable you pass won't be updated because scalars are passed by value. ::: Example 1: ```javascript const ajv = new Ajv({coerceTypes: true}) const schema = { type: "object", properties: { foo: {type: "number"}, bar: {type: "boolean"}, }, required: ["foo", "bar"], } const data = {foo: "1", bar: "false"} const validate = ajv.compile(schema) console.log(validate(data)) // true console.log(data) // { "foo": 1, "bar": false } ``` Example 2 (array coercions): ```javascript const ajv = new Ajv({coerceTypes: "array"}) const schema = { properties: { foo: {type: "array", items: {type: "number"}}, bar: {type: "boolean"}, }, } const data = {foo: "1", bar: ["false"]} const validate = ajv.compile(schema) console.log(validate(data)) // true console.log(data) // { "foo": [1], "bar": false } ``` The coercion rules, as you can see from the example, are different from JavaScript both to validate user input as expected and to have the coercion reversible (to correctly validate cases where different types are defined in subschemas of "anyOf" and other compound keywords). See [Type coercion rules](../coercion.md) for details. ================================================ FILE: docs/guide/schema-language.md ================================================ --- tags: - JTD --- # Choosing schema language [[toc]] ## JSON Type Definition Ajv supports the new specification focussed on defining cross-platform types of JSON messages/payloads - JSON Type Definition (JTD). See the informal reference of [JTD schema forms](../json-type-definition) and formal specification [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). ## JSON Schema Ajv supports most widely used drafts of JSON Schema specification. Please see the informal reference of available [JSON Schema validation keywords](../json-schema) and [specification drafts](https://json-schema.org/specification.html). ### draft-04 Draft-04 is not included in Ajv v7, because of some differences it has with the following drafts: - different schema member for schema identifier (`id` in draft-04 instead of `$id`) - different syntax of exclusiveMaximum/Minimum You can still use draft-04 schemas with Ajv v6 - while this is no longer actively developed, any security related issues would still be supported at least until 30/06/2021. To install v6: ```bash npm install ajv@6 ``` You can migrate schemas from draft-04 to draft-07 using [ajv-cli](https://github.com/ajv-validator/ajv-cli). ### draft-07 (and draft-06) These are the most widely used versions of JSON Schema specification, and they are supported with the main ajv export. ```javascript import Ajv from "ajv" const ajv = new Ajv() ``` If you need to support draft-06 schemas you need to add additional meta-schema, but you may just change (or remove) `$schema` attribute in your schemas - no other changes are needed: ```javascript const draft6MetaSchema = require("ajv/dist/refs/json-schema-draft-06.json") ajv.addMetaSchema(draft6MetaSchema) ``` ### draft 2019-09 (and draft-2020-12) The main advantage of this JSON Schema version over draft-07 is the ability to spread the definition of records that do not allow additional properties across multiple schemas. If you do not need it, you might be better off with draft-07. To use Ajv with the support of all JSON Schema draft-2019-09/2020-12 features you need to use a different export: ```javascript import Ajv2019 from "ajv/dist/2019" const ajv = new Ajv2019() ``` Optionally, you can add draft-07 meta-schema, to use both draft-07 and draft-2019-09 schemas in one Ajv instance: ```javascript const draft7MetaSchema = require("ajv/dist/refs/json-schema-draft-07.json") ajv.addMetaSchema(draft7MetaSchema) ``` Draft-2019-09 support is provided via a separate export in order to avoid increasing the bundle and generated code size for draft-07 users. With this import Ajv supports the following features: - keywords [`unevaluatedProperties`](../json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](../json-schema.md#unevaluateditems) - keywords [`dependentRequired`](../json-schema.md#dependentrequired), [`dependentSchemas`](../json-schema.md#dependentschemas), [`maxContains`/`minContains`](../json-schema.md#maxcontains-mincontains) - dynamic recursive references with [`recursiveAnchor`/`recursiveReference`] - see [Extending recursive schemas](./combining-schemas.md#extending-recursive-schemas) - draft-2019-09 meta-schema is the default. ::: warning Draft-2019-09 features performance cost (even when not used) Supporting dynamic recursive references and `unevaluatedProperties`/`unevaluatedItems` keywords adds additional generated code even to the validation functions where these features are not used (when possible, Ajv determines which properties/items are "unevaluated" at compilation time, but support for dynamic references always adds additional generated code). If you are not using these features in your schemas it is recommended to use default Ajv export with JSON-Schema draft-07 support. ::: ## Comparison Both [JSON Schema](../json-schema.md) and [JSON Type Definition](../json-type-definition.md) are cross-platform specifications with implementations in multiple programming languages that define the shape of your JSON data. You can see the difference between the two specifications in [Getting started](./getting-started) section examples. This section compares their pros/cons to help decide which specification fits your application better. ### JSON Schema **Pros**: - Wide specification adoption. - Used as part of OpenAPI specification. - Support of complex validation scenarios: - untagged unions and boolean logic - conditional schemas and dependencies - restrictions on the number ranges and the size of strings, arrays and objects - semantic validation with formats, patterns and content keywords - distribute strict record definitions across multiple schemas (with unevaluatedProperties) - Can be effectively used for validation of any JavaScript objects and configuration files. **Cons**: - Defines the collection of restrictions on the data, rather than the shape of the data. - No standard support for tagged unions. - Complex and error prone for the new users (Ajv has [strict mode](../strict-mode) enabled by default to compensate for it, but it is not cross-platform). - Some parts of specification are difficult to implement, creating the risk of implementations divergence: - reference resolution model - unevaluatedProperties/unevaluatedItems - dynamic recursive references - Internet draft status (rather than RFC) See [JSON Schema](../json-schema.md) for more information and the list of defined keywords. ### JSON Type Definition **Pros**: - Aligned with type systems of many languages - can be used to generate type definitions and efficient parsers and serializers to/from these types. - Very simple, enforcing the best practices for cross-platform JSON API modelling. - Simple to implement, ensuring consistency across implementations. - Defines the shape of JSON data via strictly defined schema forms (rather than the collection of restrictions). - Effective support for tagged unions. - Designed to protect against user mistakes. - Supports compilation of schemas to efficient [serializers and parsers](./getting-started.md#parsing-and-serializing-json) (no need to validate as a separate step). - Approved as [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). - Substantial industry adoption since it was standardized in 2020, Ajv v8.12.0 fixed all reported JTD bugs. **Cons**: - Limited, compared with JSON Schema - no support for untagged unions1, conditionals, references between different schema files2, etc. - No meta-schema in the specification3. 1 Ajv defines non-standard keyword "union" that can be used inside "metadata" object. 2 You can still combine schemas from multiple files in the application code. 3 Ajv defines meta-schema for JTD schemas. See [JSON Type Definition](../json-type-definition.md) for more information and the list of defined schema forms. ================================================ FILE: docs/guide/typescript.md ================================================ # Using with TypeScript [[toc]] ## Additional functionality Ajv takes advantage of TypeScript type system to provide additional functionality that is not possible in JavaScript: - utility types `JSONSchemaType` and `JTDSchemaType` to convert data type into the schema type to simplify writing schemas, both for [JSON Schema](../json-schema.md) (but without union support) and for [JSON Type Definition](../json-type-definition) (with tagged unions support). - utility type `JTDDataType` to convert JSON Type Definition schema into the type of data that it defines. - compiled validation functions are type guards that narrow the type after successful validation. - validation errors for JSON Schema are defined as tagged unions, for type-safe error handling. - when utility type is used, compiled JTD serializers only accept data of correct type (as they do not validate that the data is valid) and compiled parsers return correct data type. ## Utility types for schemas For the same example as in [Getting started](./getting-started): - ensure strictNullChecks is true ```typescript import Ajv, {JSONSchemaType} from "ajv" const ajv = new Ajv() interface MyData { foo: number bar?: string } const schema: JSONSchemaType = { type: "object", properties: { foo: {type: "integer"}, bar: {type: "string", nullable: true} }, required: ["foo"], additionalProperties: false } // validate is a type guard for MyData - type is inferred from schema type const validate = ajv.compile(schema) // or, if you did not use type annotation for the schema, // type parameter can be used to make it type guard: // const validate = ajv.compile(schema) const data = { foo: 1, bar: "abc" } if (validate(data)) { // data is MyData here console.log(data.foo) } else { console.log(validate.errors) } ``` ```typescript import Ajv, {JTDSchemaType} from "ajv/dist/jtd" const ajv = new Ajv() interface MyData { foo: number bar?: string } const schema: JTDSchemaType = { properties: { foo: {type: "int32"} }, optionalProperties: { bar: {type: "string"} } } // validate is a type guard for MyData - type is inferred from schema type const validate = ajv.compile(schema) // or, if you did not use type annotation for the schema, // type parameter can be used to make it type guard: // const validate = ajv.compile(schema) const data = { foo: 1, bar: "abc" } if (validate(data)) { // data is MyData here console.log(data.foo) } else { console.log(validate.errors) } ``` See [this test](https://github.com/ajv-validator/ajv/tree/master/spec/types/json-schema.spec.ts) for an advanced example. ## Utility type for JTD data type You can use JTD schema to construct the type of data using utility type `JTDDataType` ```typescript import Ajv, {JTDDataType} from "ajv/dist/jtd" const ajv = new Ajv() const schema = { properties: { foo: {type: "int32"} }, optionalProperties: { bar: {type: "string"} } } as const type MyData = JTDDataType // type inference is not supported for JTDDataType yet const validate = ajv.compile(schema) const validData = { foo: 1, bar: "abc" } if (validate(validData)) { // data is MyData here console.log(validData.foo) } else { console.log(validate.errors) } ``` ::: warning TypeScript limitation Note that it's currently not possible for `JTDDataType` to know whether the compiler is inferring timestamps as strings or Dates, and so it conservatively types any timestamp as `string | Date`. This is accurate, but often requires extra validation on the part of the user to confirm they're getting the appropriate data type. ::: ## Type-safe error handling With both [JSON Schema](../json-schema.md) and [JSON Type Definition](../json-type-definition.md), the validation error type is an open union, but it can be cast to tagged unions (using validation keyword as tag) for easier error handling. Continuing the example above: ```typescript import {DefinedError} from "ajv" // ... if (validate(data)) { // data is MyData here console.log(data.foo) } else { // The type cast is needed, as Ajv uses a wider type to allow extension // You can extend this type to include your error types as needed. for (const err of validate.errors as DefinedError[]) { switch (err.keyword) { case "type": // err type is narrowed here to have "type" error params properties console.log(err.params.type) break // ... } } } ``` ```typescript import {JTDErrorObject} from "ajv/dist/jtd" // ... if (validate(data)) { // data is MyData here console.log(data.foo) } else { // The type cast is needed, as Ajv uses a wider type to allow extension // You can extend this type to include your error types as needed. for (const err of validate.errors as JTDErrorObject[]) { switch (err.keyword) { case "type": // err type is narrowed here to have "type" error params properties console.log(err.params.type) break // ... } } } ``` ## Type-safe parsers and serializers With typescript, your compiled parsers and serializers can be type-safe, either taking their type from schema type or from type parameter passed to compilation functions. This example uses the same data and schema types as above: ```typescript import Ajv, {JTDSchemaType} from "ajv/dist/jtd" const ajv = new Ajv() interface MyData { foo: number bar?: string } const schema: JTDSchemaType = { properties: { foo: {type: "int32"} }, optionalProperties: { bar: {type: "string"} } } // serialize will only accept data compatible with MyData const serialize = ajv.compileSerializer(schema) // parse will return MyData or undefined const parse = ajv.compileParser(schema) // types of parse and serialize are inferred from schema, // they can also be defined explicitly: // const parse = ajv.compileParser(schema) const data = { foo: 1, bar: "abc" } const invalidData = { unknown: "abc" } console.log(serialize(data)) console.log(serialize(invalidData)) // type error const json = '{"foo": 1, "bar": "abc"}' const invalidJson = '{"unknown": "abc"}' console.log(parseAndLogFoo(json)) // logs property console.log(parseAndLogFoo(invalidJson)) // logs error and position function parseAndLogFoo(json: string): void { const data = parse(json) // MyData | undefined if (data === undefined) { console.log(parse.message) // error message from the last parse call console.log(parse.position) // error position in string } else { // data is MyData here console.log(data.foo) } } ``` ## Type-safe unions JSON Type Definition only supports tagged unions, so unions in JTD are fully supported for `JTDSchemaType` and `JTDDataType`. JSON Schema is more complex and so `JSONSchemaType` has limited support for type safe unions. `JSONSchemaType` will type check unions where each union element is fully specified as an element of an `anyOf` array or `oneOf` array. Additionally, unions of primitives will type check appropriately if they're combined into an array `type`, e.g. `{type: ["string", "number"]}`. ::: warning TypeScript limitation Note that due to current limitation of TypeScript, JSONSchemaType cannot verify that every element of the union is present, and the following example is still valid `const schema: JSONSchemaType = {type: "string"}`. ::: Here's a more detailed example showing several union types: ```typescript import Ajv, {JSONSchemaType} from "ajv" const ajv = new Ajv() type MyUnion = {prop: boolean} | string | number const schema: JSONSchemaType = { anyOf: [ { type: "object", properties: { prop: { type: "boolean" } }, required: ["prop"], }, { type: ["string", "number"] } ] } ``` ================================================ FILE: docs/guide/user-keywords.md ================================================ # User-defined keywords You can extend keyword available in Ajv by defining your own keywords. The advantages of defining keywords are: - allow creating validation scenarios that cannot be expressed using pre-defined keywords - simplify your schemas - help bringing a bigger part of the validation logic to your schemas - make your schemas more expressive, less verbose and closer to your application domain - implement data processors that modify your data (`modifying` option MUST be used in keyword definition) and/or create side effects while the data is being validated If a keyword is used only for side-effects and its validation result is pre-defined, use option `valid: true/false` in keyword definition to simplify both generated code (no error handling in case of `valid: true`) and your keyword functions (no need to return any validation result). ::: warning User-defined keywords make schemas non-portable When extending JSON Schema standard with additional keywords, you have several potential concerns to be aware of: - portability of your schemas - they would only work with JavaScript or TypeScript applications where you can use Ajv. - additional documentation required to maintain your schemas. ::: ::: danger Avoid using non-standard keywords with JTD schemas While it is possible to define additional keywords for JSON Type Definition schemas (these keywords can only be used in `metadata` member of the schema), it is strongly recommended not to do it - JTD is specifically designed for cross-platform APIs. ::: You can define keywords with [addKeyword](./api.md#api-addkeyword) method. Keywords are defined on the `ajv` instance level - new instances will not have previously defined keywords. Ajv allows defining keywords with: - code generation function (used by all pre-defined keywords) - validation function - compilation function - macro function Example. `range` and `exclusiveRange` keywords using compiled schema: ```javascript ajv.addKeyword({ keyword: "range", type: "number", schemaType: "array", implements: "exclusiveRange", compile: ([min, max], parentSchema) => parentSchema.exclusiveRange === true ? (data) => data > min && data < max : (data) => data >= min && data <= max, }) const schema = {range: [2, 4], exclusiveRange: true} const validate = ajv.compile(schema) console.log(validate(2.01)) // true console.log(validate(3.99)) // true console.log(validate(2)) // false console.log(validate(4)) // false ``` Several keywords (typeof, instanceof, range and propertyNames) are defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package - they can be used for your schemas and as a starting point for your own keywords. See [User-defined keywords](../keywords.md) reference for more details. ================================================ FILE: docs/guide/why-ajv.md ================================================ # Why use AJV ## Write less code **Ensure your data is valid as soon as it's received** Instead of having your data validation and sanitization logic written as lengthy code, you can declare the requirements to your data with concise, easy to read and cross-platform [JSON Schema](https://json-schema.org) or [JSON Type Definition](https://jsontypedef.com) specifications and validate the data as soon as it arrives to your application. TypeScript users can use validation functions as type guards, having type level guarantee that if your data is validated - it is correct. Read more in [Getting started](./getting-started.md) and [Using with TypeScript](./typescript.md) ## Super fast & secure **Compiles your schemas to optimized JavaScript code** Ajv generates code to turn JSON Schemas into super-fast validation functions that are efficient for v8 optimization. Currently Ajv is the fastest and the most standard compliant validator according to these benchmarks: - [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark) - 50% faster than the second place - [jsck benchmark](https://github.com/pandastrike/jsck#benchmarks) - 20-190% faster - [z-schema benchmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html) - [themis benchmark](https://cdn.rawgit.com/playlyfe/themis/master/benchmark/results.html) Ajv was designed at the time when there were no validators fully complying with JSON Schema specification, aiming to achieve the best possibly validation performance via just-in-time compilation of JSON schemas to code. Ajv achieved both speed and rigour, but initially security was an afterthought - many security flaws have been fixed thanks to the reports from its users. Ajv version 7 was rebuilt to have secure code generation embedded in its design as the primary objective - even if you use untrusted schemas (which is still not recommended) there are type-level guarantees against remote code execution. Read more in [Code generation design](../codegen.md) ## Multi-standard **Use JSON Type Definition or JSON Schema** In addition to the multiple [JSON Schema](../json-schema.md) drafts, including the latest draft 2020-12, Ajv has support for [JSON Type Definition](../json-type-definition.md) - a new [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) that offers a much simpler alternative to JSON Schema. Designed to be well-aligned with type systems, JTD has tools for both validation and type code generation for multiple languages. Read more in [Choosing schema language](./schema-language.md) ================================================ FILE: docs/json-schema.md ================================================ # JSON Schema In a simple way, JSON Schema is an object with validation keywords. The keywords and their values define what rules the data should satisfy to be valid. [[toc]] ## JSON Schema versions ### draft-07 This version is provided as default export: ```javascript const Ajv = require("ajv") const ajv = new Ajv() ``` ```typescript import Ajv from "ajv" const ajv = new Ajv() ``` ::: tip draft-07 has better performance Unless you need the new features of later versions, you would have more efficient generated code with this draft. ::: ### draft-2019-09 Ajv supports all new keywords of JSON Schema draft-2019-09: - [unevaluatedProperties](#unevaluatedproperties) - [unevaluatedItems](#unevaluateditems) - [dependentRequired](#dependentrequired) - [dependentSchemas](#dependentschemas) - [maxContains/minContains](#maxcontains--mincontains) - [$recursiveAnchor/$recursiveRef](./guide/combining-schemas.md#extending-recursive-schemas) To use draft-2019-09 schemas you need to import a different Ajv class: ```javascript const Ajv2019 = require("ajv/dist/2019") const ajv = new Ajv2019() ``` ```typescript import Ajv2019 from "ajv/dist/2019" const ajv = new Ajv2019() ``` You can use draft-07 schemas with this Ajv instance as well, draft-2019-09 is backwards compatible. If your schemas use `$schema` keyword, you need to add draft-07 meta-schema to Ajv instance: ```javascript const draft7MetaSchema = require("ajv/dist/refs/json-schema-draft-07.json") ajv.addMetaSchema(draft7MetaSchema) ``` ```typescript import * as draft7MetaSchema from "ajv/dist/refs/json-schema-draft-07.json" ajv.addMetaSchema(draft7MetaSchema) ``` ### draft-2020-12 ::: warning draft-2020-12 is not backwards compatible You cannot use draft-2020-12 and previous JSON Schema versions in the same Ajv instance. ::: Ajv supports all keywords of JSON Schema draft-2020-12: - [prefixItems](#prefixItems) that replaced array form of items keyword - changed [items](#items-in-draft-2020-12) keyword that combined parts of functionality of items and additionalItems - [$dynamicAnchor/$dynamicRef](./guide/combining-schemas.md#extending-recursive-schemas) To use draft-2020-12 schemas you need to import a different Ajv class: ```javascript const Ajv2020 = require("ajv/dist/2020") const ajv = new Ajv2020() ``` ```typescript import Ajv2020 from "ajv/dist/2020" const ajv = new Ajv2020() ``` ### draft-06 You can use JSON Schema draft-06 schemas with Ajv v7/8. If your schemas use `$schema` keyword, you need to add draft-06 meta-schema to Ajv instance. This example shows how to support both draft-06 and draft-07 schemas: ```javascript const Ajv = require("ajv") const draft6MetaSchema = require("ajv/dist/refs/json-schema-draft-06.json") const ajv = new Ajv() ajv.addMetaSchema(draft6MetaSchema) ``` ```typescript import Ajv from "ajv" import * as draft6MetaSchema from "ajv/dist/refs/json-schema-draft-06.json" const ajv = new Ajv() ajv.addMetaSchema(draft6MetaSchema) ``` ### draft-04 You can use JSON Schema draft-04 schemas with Ajv from v8.5.0 and the additional package [ajv-draft-04](https://github.com/ajv-validator/ajv-draft-04) (both ajv and ajv-draft-04 should be installed). ```javascript const Ajv = require("ajv-draft-04") const ajv = new Ajv() ``` ```typescript import Ajv from "ajv-draft-04" const ajv = new Ajv() ``` ::: warning Ajv cannot combine multiple JSON Schema versions You can only use this import with JSON Schema draft-04, you cannot combine multiple JSON Schema versions in this ajv instance. ::: ## OpenAPI support Ajv supports these additional [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md) keywords: - [nullable](#nullable) - to avoid using `type` keyword with array of types. - [discriminator](#discriminator) - to optimize validation and error reporting of tagged unions ## JSON data type ### `type` `type` keyword requires that the data is of certain type (or some of types). Its value can be a string (the allowed type) or an array of strings (multiple allowed types). Type can be: `number`, `integer`, `string`, `boolean`, `array`, `object` or `null`. **Examples** 1. _schema_: `{type: "number"}` _valid_: `1`, `1.5` _invalid_: `"abc"`, `"1"`, `[]`, `{}`, `null`, `true` 2) _schema_: `{type: "integer"}` _valid_: `1`, `2` _invalid_: `"abc"`, `"1"`, `1.5`, `[]`, `{}`, `null`, `true` 3. _schema_: `{type: ["number", "string"]}` _valid_: `1`, `1.5`, `"abc"`, `"1"` _invalid_: `[]`, `{}`, `null`, `true` All examples above are JSON Schemas that only require data to be of certain type to be valid. Most other keywords apply only to a particular type of data. If the data is of different type, the keyword will not apply and the data will be considered valid. In v7 Ajv introduced [Strict types](./strict-mode.md#strict-types) mode that makes these mistakes less likely by requiring that types are constrained with type keyword whenever another keyword that applies to specific type is used. ### nullable This keyword can be used to allow `null` value in addition to the defined `type`. Ajv supports it by default, without additional options. These two schemas are equivalent, but the first one is better supported by some tools and is also compatible with `strictTypes` option (see [Strict types](./strict-mode.md#strict-types)) ```json { "type": "string", "nullable": true } ``` and ```json { "type": ["string", "null"] } ``` ::: warning nullable does not extend enum and const If you use [enum](#enum) or [const](#const) keywords, `"nullable": true` would not extend the list of allowed values - `null` value has to be explicitly added to `enum` (and `const` would fail, unless it is `"const": null`) This is different from how `nullable` is defined in [JSON Type Definition](./json-type-definition.md), where `"nullable": true` allows `null` value in addition to any data defined by the schema. ::: ## Keywords for numbers ### `maximum` / `minimum` and `exclusiveMaximum` / `exclusiveMinimum` The value of keyword `maximum` (`minimum`) should be a number. This value is the maximum (minimum) allowed value for the data to be valid. The value of keyword `exclusiveMaximum` (`exclusiveMinimum`) should be a number. This value is the exclusive maximum (minimum) allowed value for the data to be valid (the data equal to this keyword value is invalid). ::: warning NO support for boolean keyword values Boolean values for keywords `exclusiveMaximum` (`exclusiveMinimum`) are not supported. ::: **Examples** 1. _schema_: `{type: "number", maximum: 5}` _valid_: `4`, `5` _invalid_: `6`, `7` 2. _schema_: `{type: "number", minimum: 5}` _valid_: `5`, `6` _invalid_: `4`, `4.5` 3. _schema_: `{type: "number", exclusiveMinimum: 5}` _valid_: `6`, `7` _invalid_: `4.5`, `5` ### `multipleOf` The value of the keyword should be a number. The data to be valid should be a multiple of the keyword value (i.e. the result of division of the data on the value should be integer). **Examples** 1. _schema_: `{type: "number", multipleOf: 5}` _valid_: `5`, `10` _invalid_: `1`, `4` 2) _schema_: `{type: "number", multipleOf: 2.5}` _valid_: `2.5`, `5`, `7.5` _invalid_: `1`, `4` ## Keywords for strings ### `maxLength` / `minLength` ::: warning Grapheme clusters will count as multiple characters Certain charsets have characters that are made up of multiple Unicode code points. These [grapheme clusters](https://www.unicode.org/reports/tr29/tr29-17.html#Grapheme_Cluster_Boundaries) are counted as multiple in length calculations. ::: The value of the keywords should be a number. The data to be valid should have length satisfying this rule. Unicode pairs are counted as a single character. **Examples** 1. _schema_: `{type: "string", maxLength: 5}` _valid_: `"abc"`, `"abcde"` _invalid_: `"abcdef"` 2) _schema_: `{type: "string", minLength: 2}` _valid_: `"ab"`, `"😀😀"` _invalid_: `"a"`, `"😀"` ### `pattern` The value of the keyword should be a string. The data to be valid should match the regular expression defined by the keyword value. Ajv uses `new RegExp(value, "u")` to create the regular expression that will be used to test data. **Example** _schema_: `{type: "string", pattern: "[abc]+"}` _valid_: `"a"`, `"abcd"`, `"cde"` _invalid_: `"def"`, `""` ### `format` The value of the keyword should be a string. The data to be valid should match the format with this name. Ajv does not include any formats, they can be added with [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin. **Example** _schema_: `{type: "string", format: "ipv4"}` _valid_: `"192.168.0.1"` _invalid_: `"abc"` ## Keywords for arrays ### `maxItems` / `minItems` The value of the keywords should be a number. The data array to be valid should not have more (less) items than the keyword value. **Example** _schema_: `{type: "array", maxItems: 3}` _valid_: `[]`, `[1]`, `["1", 2, "3"]` _invalid_: `[1, 2, 3, 4]` ### `uniqueItems` The value of the keyword should be a boolean. If the keyword value is `true`, the data array to be valid should have unique items. **Example** _schema_: `{type: "array", uniqueItems: true}` _valid_: `[]`, `[1]`, `["1", 2, "3"]` _invalid_: `[1, 2, 1]`, `[{a: 1, b: 2}, {b: 2, a: 1}]` ### `items` #### `items` in draft-04, -06, -07 and -2019-09 ::: warning items keyword changed in JSON Schema draft-2020-12 This section describes `items` keyword in all JSON Schema versions prior to draft-2020-12. ::: The value of the keyword should be a schema or an array of schemas. If the keyword value is a schema, then for the data array to be valid each item of the array should be valid according to the schema. In this case the `additionalItems` keyword is ignored. If the keyword value is an array, then items with indices less than the number of items in the keyword should be valid according to the schemas with the same indices. Whether additional items are valid will depend on `additionalItems` keyword. **Examples** 1. _schema_: `{type: "array", items: {type: "integer"}}` _valid_: `[1,2,3]`, `[]` _invalid_: `[1,"abc"]` 2. _schema_: ```javascript { type: "array", items: [{type: "integer"}, {type: "string"}] } ``` _valid_: `[1]`, `[1, "abc"]`, `[1, "abc", 2]`, `[]` _invalid_: `["abc", 1]`, `["abc"]` The schema in example 2 will log warning by default (see `strictTuples` option), because it defines unconstrained tuple. To define a tuple with exactly 2 elements use `minItems` and `additionalItems` keywords (see example 1 in `additionalItems`). #### `items` in draft-2020-12 ::: warning items keyword changed in JSON Schema draft-2020-12 This section describes `items` keyword in JSON draft-2020-12. ::: The value of the keyword must be a schema. For the data array to be valid: - if [prefixItems](#prefixItems) keyword is not used in the schema, then each item of the array must be valid according to the schema in `items`. - if [prefixItems](#prefixItems) keyword is used in the schema, then each item with the index starting from the size of `prefixItems` schema must be valid according to the schema in `items` **Examples** 1. _schema_: `{type: "array", items: {type: "integer"}}` _valid_: `[1,2,3]`, `[]` _invalid_: `[1,"abc"]` 2. _schema_: ```javascript { type: "array", prefixItems: [{type: "integer"}, {type: "integer"}], minItems: 2 items: false } ``` _valid_: `[1, 2]` _invalid_: `[]`, `[1]`, `[1, 2, 3]`, `[1, "abc"]` (any wrong number of items or wrong type) 3. _schema_: ```javascript { type: "array", prefixItems: [{type: "integer"}, {type: "integer"}], items: {type: "string"} } ``` _valid_: `[]`, `[1, 2]`, `[1, 2, "abc"]` _invalid_: `["abc"]`, `[1, 2, 3]` ``` _valid_: `[1]`, `[1, "abc"]`, `[1, "abc", 2]`, `[]` _invalid_: `["abc", 1]`, `["abc"]` The schema in example 3 will log warning by default (see `strictTuples` option), because it defines unconstrained tuple. To define a tuple with exactly 2 elements use `minItems` and `items` keywords (see example 2). ### `prefixItems` The value of the keyword must be an array of schemas. For the data array to be valid, the items with indices less than the number of schemas in this keyword must be valid according to the schemas with the same indices. Whether additional items are valid will depend on `items` keyword. **Examples** _schema_: ```javascript { type: "array", prefixItems: [{type: "integer"}, {type: "string"}] } ``` _valid_: `[1]`, `[1, "abc"]`, `[1, "abc", 2]`, `[]` _invalid_: `["abc", 1]`, `["abc"]` The schema in example will log warning by default (see `strictTuples` option), because it defines unconstrained tuple. To define a tuple with exactly 2 elements use [minItems](#minitems) and [items](#items-in-draft-2020-12) keywords (see example 2 in [items](#items-in-draft-2020-12)). ### `additionalItems` ::: warning additionalItems is not supported in JSON Schema draft-2020-12 To create and equivalent schema in draft-2020-12 use keywords [prefixItems](#prefixItems) and the new [items](#items-in-draft-2020-12) keyword ::: The value of the keyword should be a boolean or an object. `additionalItems` keyword is ignored if `items` keyword is not present or is an object. By default Ajv will throw exception in this case - see [Strict mode](./strict-mode.md) `additionalItems` keyword is ignored if `items` keyword has more elements than data array. If the data array has more elements than the `items` keyword value then the result of the validation depends on the value of `additionalItems` keyword: - `false`: data is invalid - `true`: data is valid - an object: data is valid if all additional items (i.e. items with indices greater or equal than "items" keyword value length) are valid according to the schema in "additionalItems" keyword. The schemas in examples 2-3 log warning by default, use option `strictTuples: false` to allow) **Examples** 1. _schema_: ```javascript { type: "array", items: [{type: "integer"}, {type: "integer"}], minItems: 2 additionalItems: false } ``` _valid_: `[1, 2]` _invalid_: `[]`, `[1]`, `[1, 2, 3]`, `[1, "abc"]` (any wrong number of items or wrong type) 2. _schema_: ```javascript { type: "array", items: [{type: "integer"}, {type: "integer"}], additionalItems: true } ``` _valid_: `[]`, `[1, 2]`, `[1, 2, 3]`, `[1, 2, "abc"]` _invalid_: `["abc"]`, `[1, "abc", 3]` 3. _schema_: ```javascript { type: "array", items: [{type: "integer"}, {type: "integer"}], additionalItems: {type: "string"} } ``` _valid_: `[]`, `[1, 2]`, `[1, 2, "abc"]` _invalid_: `["abc"]`, `[1, 2, 3]` ### `contains` The value of the keyword is a JSON Schema. The array is valid if it contains at least one item that is valid according to this schema. **Example** _schema_: `{type: "array", contains: {type: "integer"}}` _valid_: `[1]`, `[1, "foo"]`, any array with at least one integer _invalid_: `[]`, `["foo", "bar"]`, any array without integers ### `maxContains` / `minContains` The value of these keywords should be an integer. Without `contains` keyword they are ignored (logs error or throws exception in ajv [strict mode](./strict-mode.md)). The array is valid if it contains at least `minContains` items and no more than `maxContains` items that are valid against the schema in `contains` keyword. **Example** _schema_: ```javascript { type: "array", contains: {type: "integer"}, minContains: 2, maxContains: 3 } ``` _valid_: `[1, 2]`, `[1, 2, 3, "foo"]`, any array with 2 or 3 integers _invalid_: `[]`, `[1, "foo"]`, `[1, 2, 3, 4]`, any array with fewer than 2 or more than 3 integers ### `unevaluatedItems` The value of this keyword is a JSON Schema (can be a boolean). This schema will be applied to all array items that were not evaluated by other keywords for items (`items`, `additionalItems` and `contains`) in the current schema and all sub-schemas that were valid for this data instance. It includes: - all subschemas schemas in `allOf` and `$ref` keywords - valid sub-schemas in `oneOf` and `anyOf` keywords - sub-schema in `if` keyword - sub-schemas in `then` or `else` keywords that were applied based on the validation result by `if` keyword. The only scenario when this keyword would be applied to some items is when `items` keyword value is an array of schemas and `additionalItems` was not present (or did not apply, in case it was present in some invalid subschema). Some user-defined keywords can also make items "evaluated". **Example** _schema_: ```javascript { type: "array", items: [ {type: "number"}, {type: "number"} ], unevaluatedItems: false, anyOf: [ {items: [true, true, {type: "number"}]}, {items: [true, true, {type: "boolean"}]} ] } ``` _valid_: `[1, 2, 3]`, `[1, 2, true]` _invalid_: - `[1, 2]` - the third item is not present - `[1, 2, "3"]` - the third item is "unevaluated" See [tests](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/master/tests/draft2019-09/unevaluatedItems.json) for `unevaluatedItems` keyword for other examples. ## Keywords for objects ### `maxProperties` / `minProperties` The value of the keywords should be a number. The data object to be valid should have not more (less) properties than the keyword value. **Example** _schema_: `{type: "object", maxProperties: 2 }` _valid_: `{}`, `{a: 1}`, `{a: "1", b: 2}` _invalid_: `{a: 1, b: 2, c: 3}` ### `required` The value of the keyword should be an array of unique strings. The data object to be valid should contain all properties with names equal to the elements in the keyword value. **Example** _schema_: `{type: "object", required: ["a", "b"]}` _valid_: `{a: 1, b: 2}`, `{a: 1, b: 2, c: 3}` _invalid_: `{}`, `{a: 1}`, `{c: 3, d: 4}` ### `properties` The value of the keyword should be a map with keys equal to data object properties. Each value in the map should be a JSON Schema. For data object to be valid the corresponding values in data object properties should be valid according to these schemas. ::: warning Properties are not required `properties` keyword does not require that the properties mentioned in it are present in the object (see examples). ::: **Example** _schema_: ```javascript { type: "object", properties: { foo: {type: "string"}, bar: { type: "number", minimum: 2 } } } ``` _valid_: `{}`, `{foo: "a"}`, `{foo: "a", bar: 2}` _invalid_: `{foo: 1}`, `{foo: "a", bar: 1}` ### `patternProperties` The value of this keyword should be a map where keys should be regular expressions and the values should be JSON Schemas. For data object to be valid the values in data object properties that match regular expression(s) should be valid according to the corresponding schema(s). When the value in data object property matches multiple regular expressions it should be valid according to all the schemas for all matched regular expressions. ::: warning Unexpected validation results 1. `patternProperties` keyword does not require that properties matching patterns are present in the object (see examples). 2. By default, Ajv does not allow schemas where patterns in `patternProperties` match any property name in `properties` keyword - that leads to unexpected validation results. It can be allowed with option `allowMatchingProperties`. See [Strict mode](./strict-mode.md) ::: **Example** _schema_: ```javascript { type: "object", patternProperties: { "^fo.*$": {type: "string"}, "^ba.*$": {type: "number"} } } ``` _valid_: `{}`, `{foo: "a"}`, `{foo: "a", bar: 1}` _invalid_: `{foo: 1}`, `{foo: "a", bar: "b"}` ### `additionalProperties` The value of the keyword should be either a boolean or a JSON Schema. If the value is `true` the keyword is ignored. If the value is `false` the data object to be valid should not have "additional properties" (i.e. properties other than those used in "properties" keyword and those that match patterns in "patternProperties" keyword). If the value is a schema for the data object to be valid the values in all "additional properties" should be valid according to this schema. **Examples** 1. _schema_: ```javascript { type: "object", properties: { foo: {type: "number"} }, patternProperties: { "^.*r$": {type: "number"} }, additionalProperties: false } ``` _valid_: `{}`, `{foo: 1}`, `{foo: 1, bar: 2}` _invalid_: `{a: 3}`, `{foo: 1, baz: 3}` 2. _schema_: ```javascript { type: "object", properties: { foo: {type: "number"} }, patternProperties: { "^.*r$": {type: "number"} }, additionalProperties: {type: "string"} } ``` _valid_: `{}`, `{a: "b"}`, `{foo: 1}`, `{foo: 1, bar: 2}`, `{foo: 1, bar: 2, a: "b"}` _invalid_: `{a: 3}`, `{foo: 1, baz: 3}` 3. _schema_: ```javascript { type: "object", properties: { foo: {type: "number"} }, additionalProperties: false, anyOf: [ { properties: { bar: {type: "number"} } }, { properties: { baz: {type: "number"} } } ] } ``` _valid_: `{}`, `{foo: 1}` _invalid_: `{bar: 2}`, `{baz: 3}`, `{foo: 1, bar: 2}`, etc. ### `dependencies` This keyword is deprecated. The same functionality is available with keywords `dependentRequired` and `dependentSchemas`. The value of the keyword is a map with keys equal to data object properties. Each value in the map should be either an array of unique property names ("property dependency" - see [`dependentRequired`](#`dependentrequired`) keyword) or a JSON Schema ("schema dependency" - see [`dependentSchemas`](#`dependentschemas`) keyword). For property dependency, if the data object contains a property that is a key in the keyword value, then to be valid the data object should also contain all properties from the array of properties. For schema dependency, if the data object contains a property that is a key in the keyword value, then to be valid the data object itself (NOT the property value) should be valid according to the schema. **Examples** 1. _schema (property dependency)_: ```javascript { type: "object", dependencies: { foo: ["bar", "baz"] } } ``` _valid_: `{foo: 1, bar: 2, baz: 3}`, `{}`, `{a: 1}` _invalid_: `{foo: 1}`, `{foo: 1, bar: 2}`, `{foo: 1, baz: 3}` 2. _schema (schema dependency)_: ```javascript { type: "object", dependencies: { foo: { properties: { bar: {type: "number"} } } } } ``` _valid_: `{}`, `{foo: 1}`, `{foo: 1, bar: 2}`, `{a: 1}` _invalid_: `{foo: 1, bar: "a"}` ### `dependentRequired` The value of this keyword should be a map with keys equal to data object properties. Each value in the map should be an array of unique property names. If the data object contains a property that is a key in the keyword value, then to be valid the data object should also contain all properties from the corresponding array of properties in this keyword. **Example** _schema_: ```javascript { type: "object", dependentRequired: { foo: ["bar", "baz"] } } ``` _valid_: `{foo: 1, bar: 2, baz: 3}`, `{}`, `{a: 1}` _invalid_: `{foo: 1}`, `{foo: 1, bar: 2}`, `{foo: 1, baz: 3}` ### `dependentSchemas` The value of the keyword should be a map with keys equal to data object properties. Each value in the map should be a JSON Schema. If the data object contains a property that is a key in the keyword value, then to be valid the data object itself (NOT the property value) should be valid according to the corresponding schema in this keyword. **Example** _schema_: ```javascript { type: "object", dependentSchemas: { foo: { properties: { bar: {type: "number"} } } } } ``` _valid_: `{}`, `{foo: 1}`, `{foo: 1, bar: 2}`, `{a: 1}` _invalid_: `{foo: 1, bar: "a"}` ### `propertyNames` The value of this keyword is a JSON Schema. For data object to be valid each property name in this object should be valid according to this schema. **Example** _schema_ (requires `email` format from [ajv-formats](https://github.com/ajv-validator/ajv-formats)): ```javascript { type: "object", propertyNames: { format: "email" } } ``` _valid_: `{"foo@bar.com": "any", "bar@bar.com": "any"}` _invalid_: `{foo: "any value"}` ### `unevaluatedProperties` The value of this keyword is a JSON Schema (can be a boolean). This schema will be applied to all properties that were not evaluated by other keywords for properties (`properties`, `patternProperties` and `additionalProperties`) in the current schema and all sub-schemas that were valid for this data instance. It includes: - all subschemas schemas in `allOf` and `$ref` keywords - valid sub-schemas in `oneOf` and `anyOf` keywords - sub-schema in `if` keyword - sub-schemas in `then` or `else` keywords that were applied based on the validation result by `if` keyword. Some user-defined keywords can also make properties "evaluated". **Example** _schema_: ```javascript { type: "object", required: ["foo"], properties: {foo: {type: "number"}}, unevaluatedProperties: false, anyOf: [ { required: ["bar"], properties: {bar: {type: "number"}} } { required: ["baz"], properties: {baz: {type: "number"}} } ] } ``` _valid_: `{foo: 1, bar: 2}`, `{foo: 1, baz: 2}`, `{foo: 1, bar: 2, baz: 3}` _invalid_: - `{foo: 1}` - neither `bar` nor `baz` are present - `{foo: 1, bar: 2, boo: 3}` - `boo` is unevaluated - `{foo: 1, bar: 2, baz: "3"}` - not valid against the 2nd subschema, so `baz` is "unevaluated". See [tests](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/master/tests/draft2019-09/unevaluatedProperties.json) for `unevaluatedProperties` keyword for other examples. ### discriminator Ajv has a limited support for `discriminator` keyword: to optimize validation, error handling, and [modifying data](./guide/modifying-data.md) with [oneOf](#oneof) keyword. Its value should be an object with a property `propertyName` - the name of the property used to discriminate between union members. When using discriminator keyword only one subschema in `oneOf` will be used, determined by the value of discriminator property. ::: warning Use option discriminator To use `discriminator` keyword you have to use option `discriminator: true` with Ajv constructor - it is not enabled by default. ::: **Example** _schema_: ```javascript { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [ { properties: { foo: {const: "x"}, a: {type: "string"}, }, required: ["a"], }, { properties: { foo: {enum: ["y", "z"]}, b: {type: "string"}, }, required: ["b"], }, ], } ``` _valid_: `{foo: "x", a: "any"}`, `{foo: "y", b: "any"}`, `{foo: "z", b: "any"}` _invalid_: - `{}`, `{foo: 1}` - discriminator tag must be string - `{foo: "bar"}` - discriminator tag value must be in oneOf subschema - `{foo: "x", b: "b"}`, `{foo: "y", a: "a"}` - invalid object From the perspective of validation result `discriminator` is defined as no-op (that is, removing discriminator will not change the validity of the data), but errors reported in case of invalid data will be different. There are following requirements and limitations of using `discriminator` keyword: - `mapping` in discriminator object is not supported. - [oneOf](#oneof) keyword must be present in the same schema. - discriminator property should be [required](#required) either on the top level, as in the example, or in all `oneOf` subschemas. - each `oneOf` subschema must have [properties](#properties) keyword with discriminator property. The subschemas should be either inlined or included as direct references (only `$ref` keyword without any extra keywords is allowed). - schema for discriminator property in each `oneOf` subschema must be [const](#const) or [enum](#enum), with unique values across all subschemas. Not meeting any of these requirements would fail schema compilation. ## Keywords for all types ### `enum` The value of the keyword should be an array of unique items of any types. The data is valid if it is deeply equal to one of items in the array. **Example** _schema_: `{enum: [2, "foo", {foo: "bar" }, [1, 2, 3]]}` _valid_: `2`, `"foo"`, `{foo: "bar"}`, `[1, 2, 3]` _invalid_: `1`, `"bar"`, `{foo: "baz"}`, `[1, 2, 3, 4]`, any value not in enum ### `const` The value of this keyword can be anything. The data is valid if it is deeply equal to the value of the keyword. **Example** _schema_: `{const: "foo"}` _valid_: `"foo"` _invalid_: any other value The same can be achieved with `enum` keyword using the array with one item. But `const` keyword is more than just a syntax sugar for `enum`. In combination with the [$data reference](./guide/combining-schemas.md#data-reference) it allows to define equality relations between different parts of the data. This cannot be achieved with `enum` keyword even with `$data` reference because `$data` cannot be used in place of one item - it can only be used in place of the whole array in `enum` keyword. **Example** _schema_: ```javascript { type: "object", properties: { foo: {type: "number"}, bar: {const: {$data: "1/foo"}} } } ``` _valid_: `{foo: 1, bar: 1}`, `{}` _invalid_: `{foo: 1}`, `{bar: 1}`, `{foo: 1, bar: 2}` ## Compound keywords ### `not` The value of the keyword should be a JSON Schema. The data is valid if it is invalid according to this schema. **Example** _schema_: `{type: "number", not: {minimum: 3}}` _valid_: `1`, `2` _invalid_: `3`, `4` ### `oneOf` The value of the keyword should be an array of JSON Schemas. The data is valid if it matches exactly one JSON Schema from this array. Validators have to validate data against all schemas to establish validity according to this keyword. **Example** _schema_: ```javascript { type: "number", oneOf: [{maximum: 3}, {type: "integer"}] } ``` _valid_: `1.5`, `2.5`, `4`, `5` _invalid_: `2`, `3`, `4.5`, `5.5` ### `anyOf` The value of the keyword should be an array of JSON Schemas. The data is valid if it is valid according to one or more JSON Schemas in this array. Validators only need to validate data against schemas in order until the first schema matches (or until all schemas have been tried). For this reason validating against this keyword is faster than against "oneOf" keyword in most cases. **Example** _schema_: ```javascript { type: "number", anyOf: [{maximum: 3}, {type: "integer"}] } ``` _valid_: `1.5`, `2`, `2.5`, `3`, `4`, `5` _invalid_: `4.5`, `5.5` ### `allOf` The value of the keyword should be an array of JSON Schemas. The data is valid if it is valid according to all JSON Schemas in this array. **Example** _schema_: ```javascript { type: "number", allOf: [{maximum: 3}, {type: "integer"}] } ``` _valid_: `2`, `3` _invalid_: `1.5`, `2.5`, `4`, `4.5`, `5`, `5.5` ### `if`/`then`/`else` These keywords allow to implement conditional validation. Their values should be valid JSON Schemas (object or boolean). If `if` keyword is absent, the validation succeeds. If the data is valid against the sub-schema in `if` keyword, then the validation result is equal to the result of data validation against the sub-schema in `then` keyword (if `then` is absent, the validation succeeds). If the data is invalid against the sub-schema in `if` keyword, then the validation result is equal to the result of data validation against the sub-schema in `else` keyword (if `else` is absent, the validation succeeds). **Examples** 1. _schema_: ```javascript { type: "object", if: {properties: {foo: {minimum: 10}}}, then: {required: ["bar"]}, else: {required: ["baz"]} } ``` _valid_: - `{foo: 10, bar: true }` - `{}` - `{foo: 1, baz: true }` _invalid_: - `{foo: 10}` (`bar` is required) - `{foo: 10, baz: true }` (`bar` is required) - `{foo: 1}` (`baz` is required) 2) _schema_: ```javascript { type: "integer", minimum: 1, maximum: 1000, if: {minimum: 100}, then: {multipleOf: 100}, else: { if: {minimum: 10}, then: {multipleOf: 10} } } ``` _valid_: `1`, `5`, `10`, `20`, `50`, `100`, `200`, `500`, `1000` _invalid_: - `-1`, `0` (<1) - `2000` (>1000) - `11`, `57`, `123` (any integer with more than one non-zero digit) - non-integers ## Metadata keywords JSON Schema specification defines several metadata keywords that describe the schema itself but do not perform any validation. - `title` and `description`: information about the data represented by that schema - `$comment`: information for developers. With option `$comment` Ajv logs or passes the comment string to the user-supplied function. See [Options](./api.md#options). - `default`: a default value of the data instance, see [Assigning defaults](./guide/modifying-data.md#assigning-defaults). - `examples`: an array of data instances. Ajv does not check the validity of these instances against the schema. - `readOnly` and `writeOnly`: marks data-instance as read-only or write-only in relation to the source of the data (database, api, etc.). - `contentEncoding`: [RFC 2045](https://tools.ietf.org/html/rfc2045#section-6.1), e.g., "base64". - `contentMediaType`: [RFC 2046](https://datatracker.ietf.org/doc/rfc2046/), e.g., "image/png". ::: warning Ignored keywords Ajv does not implement validation of the keywords `examples`, `contentEncoding` and `contentMediaType` but it reserves them. If you want to create a plugin that implements any of them, it should remove these keywords from the instance. ::: ================================================ FILE: docs/json-type-definition.md ================================================ # JSON Type Definition This document informally describes JSON Type Definition (JTD) specification to help Ajv users to start using it. For formal definition please refer to [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). Please report any contradictions in this document with the specification. To use JTD schemas you need to import a different Ajv class: ```javascript const Ajv = require("ajv/dist/jtd") const ajv = new Ajv() ``` ```typescript import Ajv from "ajv/dist/jtd" const ajv = new Ajv() ``` [[toc]] ## JTD schema forms JTD specification defines 8 different forms that the schema for JSON can take for one of most widely used data types in JSON messages (API requests and responses). All forms require that: - schema is an object with different members, depending on the form - each form can have: - an optional member `nullable` with a boolean value that allows data instance to be JSON `null`. - an optional member `metadata` with an object value that allows to pass any additional information or extend the specification (Ajv defines keyword "union" that can be used inside `metadata`) Root schema can have member `definitions` that has a dictionary of schemas that can be references from any other schemas using form `ref` ### Type form This form defines a primitive value. It has a required member `type` and an optional members `nullable` and `metadata`, no other members are allowed. `type` can have one of the following values: - `"string"` - defines a string - `"boolean"` - defines boolean value `true` or `false` - `"timestamp"` - defines timestamp ( accepting either an [RFC3339](https://datatracker.ietf.org/doc/rfc3339/) JSON string or a Date object, configurable via the `timestamp` Ajv option) - `type` values that define integer numbers: - `"int8"` - signed byte value (-128 .. 127) - `"uint8"` - unsigned byte value (0 .. 255) - `"int16"` - signed word value (-32768 .. 32767), - `"uint16"` - unsigned word value (0 .. 65535) - `"int32"` - signed 32-bit integer value - `"uint32"` - unsigned 32-bit integer value - `type` values that define floating point numbers: - `"float32"` - 32-bit real number - `"float64"` - 64-bit real number Unlike JSON Schema, JTD does not allow defining values that can take one of several types, but they can be defined as `nullable`. **Example** ```javascript { type: "string" } ``` ### Enum form This form defines a string that can take one of the values from the list (the values in the list must be unique). It has a required member `enum` and optional members `nullable` and `metadata`, no other members are allowed. Unlike JSON Schema, JTD does not allow defining `enum` with values of any other type than string. **Example** ```javascript { enum: ["foo", "bar"] } ``` ### Elements form This form defines a homogenous array of any size (possibly empty) with the elements that satisfy a given schema. It has a required member `elements` (schema that elements should satisfy) and optional members `nullable` and `metadata`, no other members are allowed. Unlike JSON Schema, the data instance must be JSON array (without using additional `type` keyword), and there is no way to enforce the restrictions that cannot be present on type level of most languages, such as array size and uniqueness of items. **Example** Schema: ```javascript { elements: { type: "string" } } ``` Valid data: `[]`, `["foo"]`, `["foo", "bar"]` Invalid data: `["foo", 1]`, any type other than array ### Properties form This form defines record (JSON object) that has defined required and optional properties. It is required that this form has either `properties` member, or `optionalProperties`, or both, in which case the cannot have overlapping properties. Additional properties can be allowed by adding an optional boolean member `additionalProperties` with a value `true`. This form, as all other, can have optional `nullable` and `metadata` members. Unlike JSON Schema, all properties defined in `properties` schema member are required, the data instance must be JSON object (without using additional `type` keyword) and by default additional properties are not allowed (with the exception of discriminator tag - see the next section). This strictness minimises user mistakes. **Example 1.** Schema: ```javascript { properties: { foo: { type: "string" } } } ``` Valid data: `{foo: "bar"}` Invalid data: `{}`, `{foo: 1}`, `{foo: "bar", bar: 1}`, any type other than object **Example 2.** Schema: ```javascript { properties: { foo: {type: "string"} }, optionalProperties: { bar: {enum: ["1", "2"]} }, additionalProperties: true } ``` Valid data: `{foo: "bar"}`, `{foo: "bar", bar: "1"}`, `{foo: "bar", additional: 1}` Invalid data: `{}`, `{foo: 1}`, `{foo: "bar", bar: "3"}`, any type other than object **Example 3: invalid schema (overlapping required and optional properties)** ```javascript { properties: { foo: {type: "string"} }, optionalProperties: { foo: {type: "string"} } } ``` ### Discriminator form This form defines discriminated (tagged) union of different record types. It has required members `discriminator` and `mapping` and optional members `nullable` and `metadata`, no other members are allowed. The string value of `discriminator` schema member contains the name of the data member that is the tag of the union. `mapping` schema member contains the dictionary of schemas that are applied according to the value of the tag member in the data. Schemas inside `mapping` must have "properties" form. Properties forms inside `mapping` cannot be `nullable` and cannot define the same property as discriminator tag. **Example 1.** Schema: ```javascript { discriminator: "version", mapping: { "1": { properties: { foo: {type: "string"} } }, "2": { properties: { foo: {type: "uint8"} } } } } ``` Valid data: `{version: "1", foo: "1"}`, `{version: "2", foo: 1}` Invalid data: `{}`, `{foo: "1"}`, `{version: 1, foo: "1"}`, any type other than object **Example 3: invalid schema (discriminator tag member defined in mapping)** ```javascript { discriminator: "version", mapping: { "1": { properties: { version: {enum: ["1"]}, foo: {type: "string"} } }, "2": { properties: { version: {enum: ["2"]}, foo: {type: "uint8"} } } } } ``` ### Values form This form defines a homogenous dictionary where the values of members satisfy a given schema. It has a required member `values` (schema that member values should satisfy) and optional members `nullable` and `metadata`, no other members are allowed. Unlike JSON Schema, the data instance must be JSON object (without using additional `type` keyword), and there is no way to enforce size restrictions. **Example** Schema: ```javascript { values: { type: "uint8" } } ``` Valid data: `{}`, `{"foo": 1}`, `{"foo": 1, "bar": 2}` Invalid data: `{"foo": "bar"}`, any type other than object ### Ref form This form defines a reference to the schema that is present in the corresponding key in the `definitions` member of the root schema. It has a required member `ref` (member of `definitions` object in the root schema) and optional members `nullable` and `metadata`, no other members are allowed. Unlike JSON Schema, JTD does not allow to reference: - any schema fragment other than root level `definitions` member - root of the schema - there is another way to define a self-recursive schema (see Example 2) - another schema file (but you can still combine schemas from multiple files using JavaScript). **Example 1.** ```javascript { properties: { propFoo: {ref: "foo", nullable: true} }, definitions: { foo: {type: "string"} } } ``` **Example 2: self-referencing schema for binary tree** ```javascript { ref: "tree", definitions: { tree: { properties: { value: {type: "int32"} }, optionalProperties: { left: {ref: "tree"}, right: {ref: "tree"} } } } } ``` **Example 3: invalid schema (missing reference)** ```javascript { ref: "foo", definitions: { bar: {type: "string"} } } ``` ### Empty form Empty JTD schema defines the data instance that can be of any type, including JSON `null` (even if `nullable` member is not present). It cannot have any member other than `nullable` and `metadata`. ## JTDSchemaType The type `JTDSchemaType` can be used to validate that the written schema matches the type you expect to validate. This type is strict such that if typescript compiles, you should require no further type guards. The downside of this is that the types that `JTDSchemaType` can verify are limited to the types that JTD can verify. If a type doesn't verify, `JTDSchemaType` should resolve to `never`, throwing an error when you try to assign to it. This means that types like `1 | 2 | 3`, or general untagged unions (outside of unions of string literals) cannot be used with `JTDSchemaType`. ### Most Schemas Most straightforward types should work with `JTDSchemaType`, e.g. ```typescript interface MyType { num: number optionalStr?: string nullableEnum: "v1.0" | "v1.2" | null values: Record } const schema: JTDSchemaType = { properties: { num: {type: "float64"}, nullableEnum: {enum: ["v1.0", "v1.2"], nullable: true}, values: {values: {type: "int32"}}, }, optionalProperties: { optionalStr: {type: "string"}, }, } ``` will compile. Using `schema` with AJV will guarantee type safety. ### Ref Schemas Ref schemas are a little more advanced, because the types of every definition must be specified in advance. A simple ref schema is relatively straightforward: ```typescript const schema: JTDSchemaType<{val: number}, {num: number}> = { definitions: { num: {type: "float64"}, }, properties: { val: {ref: "num"}, }, } ``` note that the type of all definitions was included as a second argument to `JTDSchemaType`. This also works for recursive schemas: ```typescript type LinkedList = {val: number; next?: LinkedList} const schema: JTDSchemaType = { definitions: { node: { properties: { val: {type: "float64"}, }, optionalProperties: { next: {ref: "node"}, }, }, }, ref: "node", } ``` ### Notable Omissions `JTDSchemaType` currently validates that if the schema compiles it will verify an accurate type, but there are a few places with potentially unexpected behavior. `JTDSchemaType` doesn't verify the schema is correct. It won't reject schemas that definitions anywhere by the root, and it won't reject discriminator schemas that still define the descriminator in mapping properties. It also won't verify that enum schemas have every enum member as this isn't generally feasible in typescript yet. ## Extending JTD ### Metadata schema member Each schema form may have an optional member `metadata` that JTD reserves for implementation/application specific extensions. Ajv uses this member as a location where any non-standard keywords can be used, such as: - `union` keyword included in Ajv - any user-defined keywords, for example keywords defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package - JSON Schema keywords, as long as their names are different from standard JTD keywords. It can be used to enable a gradual migration from JSON Schema to JTD, should it be required. ::: warning Extensions are non-portable Ajv-specific extension to JTD are likely to be unsupported by other tools, so while it may simplify adoption, it undermines the cross-platform objective of using JTD. While it is ok to put some human readable information in `metadata` member, it is recommended not to add any validation logic there (even if it is supported by Ajv). ::: Additional restrictions that Ajv enforces on `metadata` schema member: - you cannot use standard JTD keywords there. While strictly speaking it is allowed by the specification, these keywords should be ignored inside `metadata` - the general approach of Ajv is to avoid anything that is ignored. - you need to define all members used in `metadata` as keywords. If they are no-op it can be done with `ajv.addKeyword("my-metadata-keyword")`. This restriction can be removed by disabling [strict mode](https://github.com/ajv-validator/ajv/blob/master/docs/strict-mode.md), without affecting the strictness of JTD - unknown keywords would still be prohibited in the schema itself. ### Union keyword Ajv defines `union` keyword that is used in the schema that validates JTD schemas ([meta-schema](https://github.com/ajv-validator/ajv/blob/master/lib/refs/jtd-schema.ts)). This keyword can be used only inside `metadata` schema member. ::: warning Union keyword is non-portable This keyword is non-standard and it is not supported in other JTD tools, so it is recommended NOT to use this keyword in schemas for your data if you want them to be cross-platform. ::: ### User-defined keywords Any user-defined keywords that can be used in JSON Schema schemas can also be used in JTD schemas, including the keywords in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package. ::: warning User-defined keywords are non-portable It is strongly recommended to only use it to simplify migration from JSON Schema to JTD and not to use non-standard keywords in the new schemas, as these keywords are not supported by any other tools. ::: ::: warning Parsing does NOT support non-standard JTD keywords compileParser method does not support non-standard JTD keywords, you will have to use JSON.parse and then validates. ::: ## Validation errors TODO ================================================ FILE: docs/keywords.md ================================================ # User defined keywords [[toc]] ## Common attributes of keyword definitions The usual interface to define all keywords has these properties: ```typescript interface _KeywordDef { keyword: string | string[] type?: JSONType | JSONType[] // data type(s) that keyword applies to, // if defined, it is usually "string", "number", "object" or "array" schemaType?: JSONType | JSONType[] // the allowed type(s) of value that keyword must have in the schema error?: { message: string | ((cxt: KeywordCxt) => Code) params?: (cxt: KeywordCxt) => Code } } ``` Keyword definitions may have additional optional properties - see [types](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) and [KeywordCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/validate/index.ts). ### Define keyword with code generation function Starting from v7 Ajv uses [CodeGen module](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) for all pre-defined keywords - see [codegen.md](./codegen.md) for details. This is the best approach for user defined keywords: - safe against code injection - best performance - the precise control over validation process - access to the parent data and the path to the currently validated data While Ajv can be safely used with plain JavaScript, it is strongly recommended to use Typescript for user-defined keywords that generate code - the prevention against code injection via untrusted schemas is partially based on the type system, not only on runtime checks. The usual keyword definition for keywords generating code extends common interface with "code" function: ```typescript interface CodeKeywordDefinition extends _KeywordDef { code: (cxt: KeywordCxt, ruleType?: string) => void // code generation function } ``` Example `even` keyword: ```typescript import {_, KeywordCxt} from Ajv ajv.addKeyword({ keyword: "even", type: "number", schemaType: "boolean", // $data: true // to support [$data reference](./guide/combining-schemas.md#data-reference), ... code(cxt: KeywordCxt) { const {data, schema} = cxt const op = schema ? _`!==` : _`===` cxt.fail(_`${data} %2 ${op} 0`) // ... the only code change needed is to use `cxt.fail$data` here }, }) const schema = {even: true} const validate = ajv.compile(schema) console.log(validate(2)) // true console.log(validate(3)) // false ``` Example `range` keyword: ```typescript import {_, nil, KeywordCxt} from Ajv ajv.addKeyword({ keyword: "range", type: "number", code(cxt: KeywordCxt) { const {schema, parentSchema, data} = cxt const [min, max] = schema const eq: Code = parentSchema.exclusiveRange ? _`=` : nil cxt.fail(_`${data} <${eq} ${min} || ${data} >${eq} ${max}`) }, metaSchema: { type: "array", items: [{type: "number"}, {type: "number"}], minItems: 2, additionalItems: false, }, }) ``` You can review pre-defined Ajv keywords in [validation](https://github.com/ajv-validator/ajv/blob/master/lib/validation) folder for more advanced examples - it is much easier to define code generation keywords than it was in the previous version of Ajv. See [KeywordCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/validate/index.ts) and [SchemaCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/index.ts) type definitions for more information about properties you can use in your keywords. ### Define keyword with "validate" function Usual keyword definition for validation keywords: ```typescript interface FuncKeywordDefinition extends _KeywordDef { validate?: SchemaValidateFunction | DataValidateFunction // DataValidateFunction requires `schema: false` option schema?: boolean // schema: false makes validate not to expect schema (DataValidateFunction) modifying?: boolean async?: boolean valid?: boolean errors?: boolean | "full" } interface SchemaValidateFunction { (schema: any, data: any, parentSchema?: AnySchemaObject, dataCxt?: DataValidationCxt): | boolean | Promise errors?: Partial[] } interface DataValidateFunction { (this: Ajv | any, data: any, dataCxt?: DataValidationCxt): boolean | Promise errors?: Partial[] } ``` The function should return validation result as boolean. It can return an array of validation errors via `.errors` property of itself (otherwise a standard error will be used). `validate` keywords are suitable for: - testing your keywords before converting them to compiled/code keywords - defining keywords that do not depend on the schema value (e.g., when the value is always `true`). In this case you can add option `schema: false` to the keyword definition and the schemas won't be passed to the validation function, it will only receive the same parameters as compiled validation function. - defining keywords where the schema is a value used in some expression. - defining keywords that support [\$data reference](./guide/combining-schemas.md#data-reference) - in this case `validate` or `code` function is required, either as the only option or in addition to `compile` or `macro`. Example: `constant` keyword (a synonym for draft-06 keyword `const`, it is equivalent to `enum` keyword with one item): ```javascript ajv.addKeyword({ keyword: "constant", validate: (schema, data) => typeof schema == "object" && schema !== null ? deepEqual(schema, data) : schema === data, errors: false, }) const schema = { constant: 2, } const validate = ajv.compile(schema) console.log(validate(2)) // true console.log(validate(3)) // false const schema = { constant: {foo: "bar"}, } const validate = ajv.compile(schema) console.log(validate({foo: "bar"})) // true console.log(validate({foo: "baz"})) // false ``` `const` keyword is already available in Ajv. ::: tip Keywords that do not define errors If the keyword does not define errors (see [Reporting errors](./api.md#reporting-errors)) pass `errors: false` in its definition; it will make generated code more efficient. ::: To add asynchronous keyword pass `async: true` in its definition. ### Define keyword with "compile" function The keyword is similar to "validate", with the difference that "compile" property has function that will be called during schema compilation and should return validation function: ```typescript interface FuncKeywordDefinition extends _KeywordDef { compile?: (schema: any, parentSchema: AnySchemaObject, it: SchemaObjCxt) => DataValidateFunction schema?: boolean // schema: false makes validate not to expect schema (DataValidateFunction) modifying?: boolean async?: boolean valid?: boolean errors?: boolean | "full" } ``` In some cases it is the best approach to define keywords, but it has the performance cost of an extra function call during validation. If keyword logic can be expressed via some other JSON Schema then `macro` keyword definition is more efficient (see below). Example. `range` and `exclusiveRange` keywords using compiled schema: ```javascript ajv.addKeyword({ keyword: "range", type: "number", compile([min, max], parentSchema) { return parentSchema.exclusiveRange === true ? (data) => data > min && data < max : (data) => data >= min && data <= max }, errors: false, metaSchema: { // schema to validate keyword value type: "array", items: [{type: "number"}, {type: "number"}], minItems: 2, additionalItems: false, }, }) const schema = { range: [2, 4], exclusiveRange: true, } const validate = ajv.compile(schema) console.log(validate(2.01)) // true console.log(validate(3.99)) // true console.log(validate(2)) // false console.log(validate(4)) // false ``` See note on errors and asynchronous keywords in the previous section. ### Define keyword with "macro" function Keyword definition: ```typescript interface MacroKeywordDefinition extends FuncKeywordDefinition { macro: (schema: any, parentSchema: AnySchemaObject, it: SchemaCxt) => AnySchema } ``` "Macro" function is called during schema compilation. It is passed schema, parent schema and [schema compilation context](#schema-compilation-context) and it should return another schema that will be applied to the data in addition to the original schema. It is an efficient approach (in cases when the keyword logic can be expressed with another JSON Schema), because it is usually easy to implement and there is no extra function call during validation. In addition to the errors from the expanded schema macro keyword will add its own error in case validation fails. Example. `range` and `exclusiveRange` keywords from the previous example defined with macro: ```javascript ajv.addKeyword({ keyword: "range", type: "number", macro: ([minimum, maximum]) => ({minimum, maximum}), // schema with keywords minimum and maximum // metaSchema: the same as in the example above }) ``` Macro keywords can be recursive - i.e. return schemas containing the same keyword. See the example of defining a recursive macro keyword `deepProperties` in the [test](https://github.com/ajv-validator/ajv/blob/master/spec/keyword.spec.ts#L316). ## Schema compilation context Schema compilation context [SchemaCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/index.ts) is available in property `it` of [KeywordCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/validate/index.ts) (and it is also the 3rd parameter of `compile` and `macro` keyword functions). See types in the source code on the properties you can use in this object. ## Validation time variables All function scoped variables available during validation are defined in [names](https://github.com/ajv-validator/ajv/blob/master/lib/compile/names.ts). ## Reporting errors All keywords can define error messages with `KeywordErrorDefinition` object passed as `error` property of keyword definition: ```typescript interface KeywordErrorDefinition { message: string | ((cxt: KeywordErrorCxt) => Code) params?: (cxt: KeywordErrorCxt) => Code } ``` `code` keywords can pass parameters to these functions via `cxt.setParams` (see implementations of pre-defined keywords), other keywords can only set a string message this way. Another approach for reporting errors can be used for `validate` and `compile` keyword - they can define errors by assigning them to `.errors` property of the validation function. Asynchronous keywords can return promise that rejects with `new Ajv.ValidationError(errors)`, where `errors` is an array of validation errors (if you don't want to create errors in asynchronous keyword, its validation function can return the promise that resolves with `false`). Each error object in `errors` array should at least have properties `keyword`, `message` and `params`, other properties will be added. If keyword doesn't define or return errors, the default error will be created in case the keyword fails validation. ================================================ FILE: docs/news/2020-08-14-mozilla-grant-openjs-foundation.md ================================================ --- news: true title: Mozilla MOSS grant and OpenJS Foundation date: 2020-08-14 --- [](https://www.mozilla.org/en-US/moss/)[](https://openjsf.org/blog/2020/08/14/ajv-joins-openjs-foundation-as-an-incubation-project/) Ajv has been awarded a grant from Mozilla’s [Open Source Support (MOSS) program](https://www.mozilla.org/en-US/moss/) in the “Foundational Technology” track! It will sponsor the development of Ajv support of [JSON Schema version 2019-09](https://tools.ietf.org/html/draft-handrews-json-schema-02) and of [JSON Type Definition (RFC8927)](https://datatracker.ietf.org/doc/rfc8927/). Ajv also joined [OpenJS Foundation](https://openjsf.org/) – having this support will help ensure the longevity and stability of Ajv for all its users. This [blog post](https://www.poberezkin.com/posts/2020-08-14-ajv-json-validator-mozilla-open-source-grant-openjs-foundation.html) has more details. I am looking for the long term maintainers of Ajv – working with [ReadySet](https://www.thereadyset.co/), also sponsored by Mozilla, to establish clear guidelines for the role of a "maintainer" and the contribution standards, and to encourage a wider, more inclusive, contribution from the community. ================================================ FILE: docs/news/2020-12-15-ajv-version-7-released.md ================================================ --- news: true title: Ajv version 7 is released! date: 2020-12-15 --- Ajv version 7 has these new features: - support of JSON Schema draft-2019-09 features: unevaluatedProperties and unevaluatedItems, dynamic recursive references and other additional keywords. - to reduce the mistakes in JSON schemas and unexpected validation results, strict mode is added - it prohibits ignored or ambiguous JSON Schema elements. - to make code injection from untrusted schemas impossible, code generation is fully re-written to be safe and to allow code optimization (compiled schema code size is reduced by more than 10%). - to simplify Ajv extensions, the new keyword API that is used by pre-defined keywords is available to user-defined keywords - it is much easier to define any keywords now, especially with subschemas. [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package was updated to use the new API (in [v4.0.0](https://github.com/ajv-validator/ajv-keywords/releases/tag/v4.0.0)) - schemas are compiled to ES6 code (ES5 code generation is also supported with an option). - to improve reliability and maintainability the code is migrated to TypeScript. **Please note**: - the support for JSON-Schema draft-04 is removed - if you have schemas using "id" attributes you have to replace them with "\$id" (or continue using [Ajv v6](https://github.com/ajv-validator/ajv/tree/v6) that will be supported until 02/28/2021). - all formats are separated to ajv-formats package - they have to be explicitly added if you use them. See [release notes](https://github.com/ajv-validator/ajv/releases/tag/v7.0.0) for the details. To install the new version: ```bash npm install ajv ``` See [Getting started](/guide/getting-started.md) for code examples. ================================================ FILE: docs/news/2021-03-07-ajv-supports-json-type-definition.md ================================================ --- news: true title: Ajv supports JSON Type Definition date: 2021-03-07 --- JSON Type Definition (JTD) is a new specification for defining JSON structures that is very simple to use, comparing with JSON Schema, less error prone, and it is published as [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). See Choosing schema language for a detailed comparison between JSON Schema and JSON Type definition and informal specification. In addition to validation, Ajv also supports: - generation of [serializers](/api.html#jtd-serialize) and [parsers](/api.html#jtd-parse) from JTD schemas/ This is more efficient than native JSON serialization/parsing - you can combine JSON string parsing and validation in one function call. - utility type [JTDSchemaType](/guide/typescript.html#utility-types-for-schemas) to convert your data type to the type of JTD schema and [JTDDataType](/guide/typescript.html#utility-type-for-jtd-data-type) to convert the type of schema to the type of data. ================================================ FILE: docs/news/2021-03-27-ajv-version-8-released.md ================================================ --- news: true title: Ajv version 8 is released! date: 2021-03-27 --- Ajv version 8 has these new features: - support of JSON Schema draft-2020-12: prefixItems keyword and changed semantics of items keyword, dynamic recursive references. - OpenAPI discriminator keyword. - improved JSON Type Definition support: - errors consistent with JTD specification. - error objects with additional properties to simplify error handling - internationalized error messages with [ajv-i18n](/packages/ajv-i18n) - TypeScript: support type unions in [JSONSchemaType](/guide/typescript.html#type-safe-unions) See [release notes](https://github.com/ajv-validator/ajv/releases/tag/v8.0.0) for the details. To install the new version: ```bash npm install ajv ``` See [Getting started](/guide/getting-started.md) for code examples. ================================================ FILE: docs/news/2021-04-24-ajv-online-event.md ================================================ --- news: true title: "Ajv online event - May 20, 10am PT / 6pm UK" date: 2021-04-24 more: false --- We will talk about: - new features of Ajv version 8. - the improvements sponsored by Mozilla's MOSS grant. - how Ajv is used in JavaScript applications. Speakers: - [Evgeny Poberezkin](https://github.com/epoberezkin), the creator of Ajv. - [Mehan Jayasuriya](https://github.com/mehan), Program Officer at Mozilla Foundation, leading the [MOSS](https://www.mozilla.org/en-US/moss/) and other programs investing in the open source and community ecosystems. - [Matteo Collina](https://github.com/mcollina), Technical Director at NearForm and Node.js Technical Steering Committee member, creator of Fastify web framework. - [Kin Lane](https://github.com/kinlane), Chief Evangelist at Postman. Studying the tech, business & politics of APIs since 2010. Presidential Innovation Fellow during the Obama administration. - [Ulysse Carion](https://github.com/ucarion), the creator of JSON Type Definition specification. [Gajus Kuizinas](https://github.com/gajus) will host the event. Please [register here](https://us02web.zoom.us/webinar/register/4216192074976/WN_erJ_t4ICTHOnGC1SOybNnw). ================================================ FILE: docs/news/2021-05-24-ajv-online-event-video.md ================================================ --- news: true title: Ajv online event video uploaded date: 2021-05-24 more: false --- Huge thanks to everybody who joined, and to the speakers! The video of the event is [available on YouTube](https://www.youtube.com/watch?v=KxSKqXEBB7A). ================================================ FILE: docs/news/2021-07-22-ajv-microsoft-foss-fund-award.md ================================================ --- news: true title: Microsoft FOSS award date: 2021-07-22 more: false --- Ajv was awarded a sponsorship from [Microsoft FOSS fund](https://github.com/microsoft/foss-fund/blob/main/README.md#2021) - huge thanks to Microsoft and the engineers who voted to support Ajv development. This award will contribute to a long term maintenance of Ajv. ================================================ FILE: docs/news/README.md ================================================ --- newsIndex: true editLink: false --- # Ajv News ================================================ FILE: docs/options.md ================================================ # Ajv options [[toc]] ## Usage This page describes properties of the options object that can be passed to Ajv constructor. For example, to report all validation errors (rather than failing on the first errors) you should pass `allErrors` option to constructor: ```javascript const ajv = new Ajv({allErrors: true}) ``` ## Option defaults ::: tip Do NOT pass default options Passing the value below for some of the options is equivalent to not passing this option at all. There is no need to pass default option values - it is recommended to only pass option values that are different from defaults. ::: ```javascript // see types/index.ts for actual types const defaultOptions = { // strict mode options (NEW) strict: undefined, // * strictSchema: true, // * strictNumbers: true, // * strictTypes: "log", // * strictTuples: "log", // * strictRequired: false, // * allowUnionTypes: false, // * allowMatchingProperties: false, // * validateFormats: true, // * // validation and reporting options: $data: false, // * allErrors: false, verbose: false, discriminator: false, // * unicodeRegExp: true, // * timestamp: undefined // ** parseDate: false // ** allowDate: false // ** int32range: true // ** $comment: false, // * formats: {}, keywords: {}, schemas: {}, logger: undefined, loadSchema: undefined, // *, function(uri: string): Promise {} // options to modify validated data: removeAdditional: false, useDefaults: false, // * coerceTypes: false, // * // advanced options: meta: true, validateSchema: true, addUsedSchema: true, inlineRefs: true, passContext: false, loopRequired: 200, // * loopEnum: 200, // NEW ownProperties: false, multipleOfPrecision: undefined, // * messages: true, // false with JTD uriResolver: undefined, code: { // NEW es5: false, esm: false, lines: false, source: false, process: undefined, // (code: string) => string optimize: true, regExp: RegExp }, } ``` \* only with JSON Schema \** only with JSON Type Definition ## Strict mode options ### strict By default Ajv executes in strict mode, that is designed to prevent any unexpected behaviours or silently ignored mistakes in schemas (see [Strict Mode](./strict-mode.md) for more details). It does not change any validation results, but it makes some schemas invalid that would be otherwise valid according to JSON Schema specification. Option values: - `true` - throw an exception when any strict mode restriction is violated. - `"log"` - log warning when any strict mode restriction is violated. - `false` - ignore all strict mode violations. - `undefined` (default) - use defaults for options strictSchema, strictNumbers, strictTypes, strictTuples and strictRequired. ### strictSchema Prevent unknown keywords, formats etc. (see [Strict schema](./strict-mode.md#strict-schema)) Option values: - `true` (default) - throw an exception when any strict schema restriction is violated. - `"log"` - log warning when any strict schema restriction is violated. - `false` - ignore all strict schema violations. ### strictNumbers Whether to accept `NaN` and `Infinity` as number types during validation. Option values: - `true` (default) - fail validation if `NaN` or `Infinity` is passed where number is expected. - `false` - allow `NaN` and `Infinity` as number. ### strictTypes See [Strict types](./strict-mode.md#strict-types) Option values: - `true` - throw an exception when any strict types restriction is violated. - `"log"` (default) - log warning when any strict types restriction is violated. - `false` - ignore all strict types violations. ### strictTuples See [Unconstrained tuples](./strict-mode.md#unconstrained-tuples) Option values: - `true` - throw an exception when any strict tuples restriction is violated. - `"log"` (default) - log warning when any strict tuples restriction is violated. - `false` - ignore all strict tuples violations. ### strictRequired See [Defined required properties](./strict-mode.md#defined-required-properties) Option values: - `true` - throw an exception when strict required restriction is violated. - `"log"` - log warning when strict required restriction is violated. - `false` (default) - ignore strict required violations. ### allowUnionTypes Pass true to allow using multiple non-null types in "type" keyword (one of `strictTypes` restrictions). see [Strict types](./strict-mode.md#strict-types) ### allowMatchingProperties Pass true to allow overlap between "properties" and "patternProperties". Does not affect other strict mode restrictions. See [Strict Mode](./strict-mode.md). ### validateFormats Format validation. Option values: - `true` (default) - validate formats (see [Formats](./guide/formats.md)). In [strict mode](./strict-mode.md) unknown formats will throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](./guide/combining-schemas.md#data-reference)). - `false` - do not validate any format keywords (TODO they will still collect annotations once supported). ## Validation and reporting options ### $data Support [\$data references](./guide/combining-schemas.md#data-reference). Draft 6 meta-schema that is added by default will be extended to allow them. If you want to use another meta-schema you need to use $dataMetaSchema method to add support for $data reference. See [API](#ajv-constructor-and-methods). ### allErrors Check all rules collecting all errors. Default is to return after the first error. ### verbose Include the reference to the part of the schema (`schema` and `parentSchema`) and validated data in errors (false by default). ### discriminator Support [discriminator keyword](./json-schema.md#discriminator) from [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md). ### unicodeRegExp By default Ajv uses unicode flag "u" with "pattern" and "patternProperties", as per JSON Schema spec. See [RegExp.prototype.unicode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode) . Option values: - `true` (default) - use unicode flag "u". - `false` - do not use flag "u". ### timestamp Defines which Javascript types will be accepted for the [JTD timestamp type](./json-type-definition#type-form). By default Ajv will accept both Date objects and [RFC3339](https://datatracker.ietf.org/doc/rfc3339/) strings. You can specify allowed values with the option `timestamp: "date"` or `timestamp: "string"`. ### parseDate Defines how date-time strings are parsed by [JTD parsers](./api.md#jtd-parse). By default Ajv parses date-time strings as string. Use `parseDate: true` to parse them as Date objects. ### allowDate Defines how date-time strings are parsed and validated. By default Ajv only allows full date-time strings, as required by JTD specification. Use `allowDate: true` to allow date strings both for validation and for parsing. ::: warning Option allowDate is not portable This option makes JTD validation and parsing more permissive and non-standard. The date strings without time part will be accepted by Ajv, but will be rejected by other JTD validators. ::: ### specialNumbers Defines how special case numbers `Infinity`, `-Infinity` and `NaN` are handled. Option values: - `"fast"` - (default): Do not treat special numbers differently to normal numbers. This is the fastest method but also can produce invalid JSON if the data contains special numbers. - `"null"` - Special numbers will be serialized to `null` which is the correct behavior according to the JSON spec and is also the same behavior as `JSON.stringify`. ::: warning The default behavior can produce invalid JSON Using `specialNumbers: "fast" or undefined` can produce invalid JSON when there are any special case numbers in the data. ::: ### int32range Can be used to disable range checking for `int32` and `uint32` types. By default Ajv limits the range of these types to `[-2**31, 2**31 - 1]` for `int32` and to `[0, 2**32-1]` for `uint32` when validating and parsing. With option `int32range: false` Ajv only requires that `uint32` is non-negative, otherwise does not check the range. Parser will limit the number size to 16 digits (approx. `2**53` - safe integer range). ::: warning Option int32range is not portable This option makes JTD validation and parsing more permissive and non-standard. The integers within a wider range will be accepted by Ajv, but will be rejected by other JTD validators. ::: ### $comment Log or pass the value of `$comment` keyword to a function. Option values: - `false` (default): ignore \$comment keyword. - `true`: log the keyword value to console. - function: pass the keyword value, its schema path and root schema to the specified function ### formats An object with format definitions. Keys and values will be passed to `addFormat` method. Pass `true` as format definition to ignore some formats. ### keywords An array of keyword definitions or strings. Values will be passed to `addKeyword` method. ### schemas An array or object of schemas that will be added to the instance. In case you pass the array the schemas must have IDs in them. When the object is passed the method `addSchema(value, key)` will be called for each schema in this object. ### logger Sets the logging method. Default is the global `console` object that should have methods `log`, `warn` and `error`. See [Error logging](./api.md#error-logging). Option values: - logger instance - it should have methods `log`, `warn` and `error`. If any of these methods is missing an exception will be thrown. - `false` - logging is disabled. ### loadSchema Asynchronous function that will be used to load remote schemas when `compileAsync` [method](#api-compileAsync) is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept remote schema uri as a parameter and return a Promise that resolves to a schema. See example in [Asynchronous compilation](./guide/managing-schemas.md#asynchronous-schema-compilation). ## Options to modify validated data ### removeAdditional Remove additional properties - see example in [Removing additional properties](./guide/modifying-data.md#removing-additional-properties). This option is not used if schema is added with `addMetaSchema` method. Option values: - `false` (default) - not to remove additional properties - `"all"` - all additional properties are removed, regardless of `additionalProperties` keyword in schema (and no validation is made for them). - `true` - only additional properties with `additionalProperties` keyword equal to `false` are removed. - `"failing"` - additional properties that fail schema validation will be removed (where `additionalProperties` keyword is `false` or schema). ### useDefaults Replace missing or undefined properties and items with the values from corresponding `default` keywords. Default behaviour is to ignore `default` keywords. This option is not used if schema is added with `addMetaSchema` method. See examples in [Assigning defaults](./guide/modifying-data.md#assigning-defaults). Option values: - `false` (default) - do not use defaults - `true` - insert defaults by value (object literal is used). - `"empty"` - in addition to missing or undefined, use defaults for properties and items that are equal to `null` or `""` (an empty string). ### coerceTypes Change data type of data to match `type` keyword. See the example in [Coercing data types](./guide/modifying-data.md#coercing-data-types) and [coercion rules](./coercion.md). Option values: - `false` (default) - no type coercion. - `true` - coerce scalar data types. - `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema). ## Advanced options ### meta Add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default). If an object is passed, it will be used as the default meta-schema for schemas that have no `$schema` keyword. This default meta-schema MUST have `$schema` keyword. ### validateSchema Validate added/compiled schemas against meta-schema (true by default). `$schema` property in the schema can be http://json-schema.org/draft-07/schema or absent (draft-07 meta-schema will be used) or can be a reference to the schema previously added with `addMetaSchema` method. Option values: - `true` (default) - if the validation fails, throw the exception. - `"log"` - if the validation fails, log error. - `false` - skip schema validation. ### addUsedSchema By default methods `compile` and `validate` add schemas to the instance if they have `$id` (or `id`) property that doesn't start with "#". If `$id` is present and it is not unique the exception will be thrown. Set this option to `false` to skip adding schemas to the instance and the `$id` uniqueness check when these methods are used. This option does not affect `addSchema` method. ### inlineRefs Affects compilation of referenced schemas. Option values: - `true` (default) - the referenced schemas that don't have refs in them are inlined, regardless of their size - it improves performance. - `false` - to not inline referenced schemas (they will always be compiled as separate functions). - integer number - to limit the maximum number of keywords of the schema that will be inlined (to balance the total size of compiled functions and performance). ### passContext Pass validation context to _compile_ and _validate_ keyword functions. If this option is `true` and you pass some context to the compiled validation function with `validate.call(context, data)`, the `context` will be available as `this` in your keywords. By default `this` is Ajv instance. ### loopRequired By default `required` keyword is compiled into a single expression (or a sequence of statements in `allErrors` mode) up to 200 required properties. Pass integer to set a different number of properties above which `required` keyword will be validated in a loop (with a smaller validation function size and worse performance). ### loopEnum By default `enum` keyword is compiled into a single expression with up to 200 allowed values. Pass integer to set the number of values above which `enum` keyword will be validated in a loop (with a smaller validation function size and worse performance). ### ownProperties By default Ajv iterates over all enumerable object properties; when this option is `true` only own enumerable object properties (i.e. found directly on the object rather than on its prototype) are iterated. Contributed by @mbroadst. ### multipleOfPrecision By default `multipleOf` keyword is validated by comparing the result of division with `parseInt()` of that result. It works for dividers that are bigger than 1. For small dividers such as 0.01 the result of the division is usually not integer (even when it should be integer, see issue [#84](https://github.com/ajv-validator/ajv/issues/84)). If you need to use fractional dividers set this option to some positive integer N to have `multipleOf` validated using this formula: `Math.abs(Math.round(division) - division) < 1e-N` (it is slower but allows for float arithmetic deviations). ### messages Include human-readable messages in errors. `true` by default. `false` can be passed when messages are generated outside of Ajv code (e.g. with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n)). ### uriResolver By default `uriResolver` is undefined and relies on the embedded uriResolver [fast-uri](https://github.com/fastify/fast-uri). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [uri-js](https://github.com/garycourt/uri-js). ### code Code generation options: ```typescript type CodeOptions = { es5?: boolean // to generate es5 code - by default code is es6, with "for-of" loops, "let" and "const" esm?: boolean // how functions should be exported - by default CJS is used, so the validate function(s) // file can be `required`. Set this value to true to export the validate function(s) as ES Modules, enabling // bunlers to do their job. lines?: boolean // add line-breaks to code - to simplify debugging of generated functions source?: boolean // add `source` property (see Source below) to validating function. process?: (code: string, schema?: SchemaEnv) => string // an optional function to process generated code // before it is passed to Function constructor. // It can be used to either beautify or to transpile code. optimize?: boolean | number // code optimization flag or number of passes, 1 pass by default, // code optimizations reduce the size of the generated code (bytes, based on the tests) by over 10%, // the number of code tree nodes by nearly 17%. // You would almost never need more than one optimization pass, unless you have some really complex schemas - // the second pass in the tests (it has quite complex schemas) only improves optimization by less than 0.1%. // See [Code optimization](./codegen.md#code-optimization) for details. formats?: Code // Code snippet created with `_` tagged template literal that contains all format definitions, // it can be the code of actual definitions or `require` call: // _`require("./my-formats")` regExp: RegExpEngine // Pass non-standard RegExp engine to mitigate ReDoS, e.g. node-re2. // During validation of a schema, code.regExp will be // used to match strings against regular expressions. // The supplied function must support the interface: // regExp(regex, unicodeFlag).test(string) => boolean } type Source = { code: string // unlike func.toString() it includes assignments external to function scope scope: Scope // see Code generation (TODO) } ``` ================================================ FILE: docs/packages/README.md ================================================ # Extending Ajv ## Plugins Ajv can be extended with plugins that add [user defined schema keywords](../guide/user-keywords.md), [validation formats](../guide/formats.md) or functions to process generated code. When such plugin is published as npm package it is recommended that it follows these conventions: - it exports a function that accepts ajv instance as the first parameter - it allows using plugins with [ajv-cli](./ajv-cli.md). - this function returns the same instance to allow chaining. - this function can accept an optional configuration as the second parameter. You can import `Plugin` interface from ajv if you use Typescript. If you have published a useful plugin please submit a PR to add it to the next section. ## Related packages - [ajv-formats](https://github.com/ajv-validator/ajv-formats) - formats defined in JSON Schema specification - [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) - additional validation keywords (select, typeof, etc.) - [ajv-errors](https://github.com/ajv-validator/ajv-errors) - defining error messages in the schema - [ajv-i18n](https://github.com/ajv-validator/ajv-i18n) - internationalised error messages - [ajv-cli](https://github.com/ajv-validator/ajv-cli) - command line interface - [ajv-bsontype](https://github.com/BoLaMN/ajv-bsontype) - MongoDB's bsonType formats - [ajv-formats-draft2019](https://github.com/luzlab/ajv-formats-draft2019) - formats for draft-2019-09 that are not included in [ajv-formats](./ajv-formats.md) (`idn-hostname`, `idn-email`, `iri` and `iri-reference`) - [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) - keywords $merge and $patch ================================================ FILE: docs/security.md ================================================ # Security considerations JSON Schema, if properly used, can replace data sanitisation. It doesn't replace other API security considerations. It also introduces additional security aspects to consider. [[toc]] ## Security contact To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues. ## Untrusted schemas Ajv treats JSON schemas as trusted as your application code. This security model is based on the most common use case, when the schemas are static and bundled together with the application. If your schemas are received from untrusted sources (or generated from untrusted data) there are several scenarios you need to prevent: - compiling schemas can cause stack overflow (if they are too deep) - compiling schemas can be slow (e.g. [#557](https://github.com/ajv-validator/ajv/issues/557)) - validating certain data can be slow It is difficult to predict all the scenarios, but at the very least it may help to limit the size of untrusted schemas (e.g. limit JSON string length) and also the maximum schema object depth (that can be high for relatively small JSON strings). You also may want to mitigate slow regular expressions in `pattern` and `patternProperties` keywords. Regardless the measures you take, using untrusted schemas increases security risks. ## Circular references in JavaScript objects Ajv does not support schemas and validated data that have circular references in objects. See [issue #802](https://github.com/ajv-validator/ajv/issues/802). An attempt to compile such schemas or validate such data would cause stack overflow (or will not complete in case of asynchronous validation). Depending on the parser you use, untrusted data can lead to circular references. ## Security risks of trusted schemas Some keywords in JSON Schemas can lead to very slow validation for certain data. These keywords include (but may be not limited to): - `pattern` and `format` for large strings - in some cases using `maxLength` can help mitigate it, but certain regular expressions can lead to exponential validation time even with relatively short strings (see [ReDoS attack](#redos-attack)). - `patternProperties` for large property names - use `propertyNames` to mitigate, but some regular expressions can have exponential evaluation time as well. - `uniqueItems` for large non-scalar arrays - use `maxItems` to mitigate ::: danger Do NOT use allErrors in production The suggestions above to prevent slow validation would only work if you do NOT use `allErrors: true` in production code (using it would continue validation after validation errors). ::: You can validate your JSON schemas against [this meta-schema](https://github.com/ajv-validator/ajv/blob/master/lib/refs/json-schema-secure.json) to check that these recommendations are followed: ```javascript ajv = new Ajv({strictTypes: false}) // this option is required for this schema const isSchemaSecure = ajv.compile(require("ajv/lib/refs/json-schema-secure.json")) const schema1 = {format: "email"} isSchemaSecure(schema1) // false const schema2 = {format: "email", maxLength: MAX_LENGTH} isSchemaSecure(schema2) // true ``` ::: danger Untrusted data Following all these recommendation is not a guarantee that validation using of untrusted data is safe - it can still lead to some undesirable results. ::: ## ReDoS attack Certain regular expressions can lead to the exponential evaluation time even with relatively short strings. Please assess the regular expressions you use in the schemas on their vulnerability to this attack - see [safe-regex](https://github.com/substack/safe-regex), for example. By default, Ajv uses the regex engine built into Node.js. This engine has exponential worst-case performance. This performance (and ReDoS attacks) can be mitigated by using a linear-time regex engine. Ajv supports the use of a third-party regex engine for this purpose. To use a third-party regex engine in Ajv, set the ajv.opts.code.regExp property to that regex engine during instantiation. Here we use Google’s RE2 engine as an example. ``` const Ajv = require("ajv") const RE2 = require("re2") const ajv = new Ajv({code: {regExp: RE2}}) ``` For details about the interface of the `regexp` option, see options.md under the docs folder. Although linear-time regex engines eliminate ReDoS vulnerabilities, changing a regex engine carries some risk, including: - Minor changes in regex syntax. - Minor changes in regex semantics. For example, RE2 always interprets regexes in Unicode, and disagrees with JavaScript in its definition of whitespace. To avoid regressions, develop and test your regexes in the same regex engine that you use in production. - May not support some advanced features, such as look-aheads or back-references. - May have (minor) common-case performance degradation. - Increases size of distributable (e.g. RE2 includes a non-trivial C component). ::: warning ReDoS attack Some formats that [ajv-formats](https://github.com/ajv-validator/ajv-formats) package implements use [regular expressions](https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts) that can be vulnerable to ReDoS attack. ::: If you use Ajv to validate data from untrusted sources **it is strongly recommended** to consider the following: - making assessment of "format" implementations in [ajv-formats](https://github.com/ajv-validator/ajv-formats). - passing `"fast"` option to ajv-formats plugin (see its docs) that simplifies some of the regular expressions (although it does not guarantee that they are safe). - replacing format implementations provided by ajv-formats with your own implementations of "format" keyword that either use different regular expressions or another approach to format validation. Please see [addFormat](https://github.com/ajv-validator/ajv/blob/master/docs/api.md#api-addformat) method. - disabling format validation by ignoring "format" keyword with option `format: false` Whatever mitigation you choose, please assume all formats provided by ajv-formats as potentially unsafe and make your own assessment of their suitability for your validation scenarios. ## Content Security Policy When using Ajv in a browser page with enabled Content Security Policy (CSP), `script-src` directive must include `'unsafe-eval'`. ::: danger Cross-site scripting attacks `unsafe-eval` is NOT recommended in a secure CSP[[1]](https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-eval), as it has the potential to open the document to cross-site scripting (XSS) attacks. ::: In order to use Ajv without relaxing CSP, you can [compile the schemas using CLI](https://github.com/ajv-validator/ajv-cli#compile-schemas) or programmatically in your build code - see [Standalone validation code](./standalone.md). Compiled JavaScript file can export one or several validation functions that have the same code as the schemas compiled at runtime. ================================================ FILE: docs/standalone.md ================================================ # Standalone validation code [[toc]] Ajv supports generating standalone validation functions from JSON Schemas at compile/build time. These functions can then be used during runtime to do validation without initialising Ajv. It is useful for several reasons: - to reduce the browser bundle size - Ajv is not included in the bundle (although if you have a large number of schemas the bundle can become bigger - when the total size of generated validation code is bigger than Ajv code). - to reduce the start-up time - the validation and compilation of schemas will happen during build time. - to avoid dynamic code evaluation with Function constructor (used for schema compilation) - when it is prohibited by the browser page [Content Security Policy](./security.md#content-security-policy). This functionality in Ajv v7 supersedes the deprecated package ajv-pack that can be used with Ajv v6. All schemas, including those with recursive references, formats and user-defined keywords are supported, with few [limitations](#configuration-and-limitations). ## Two-step process The **first step** is to **generate** the standalone validation function code. This is done at compile/build time of your project and the output is a generated JS file. The **second step** is to **use** the generated JS validation function. There are two methods to generate the code, using either the Ajv CLI or the Ajv JS library. There are also a few different options that can be passed when generating code. Below is just a highlight of a few options: - Set the `code.source` (JS) value to true or use the `compile` (CLI) command to generate standalone code. - The standalone code can be generated in either ES5 or ES6, it defaults to ES6. Set the `code.es5` (JS) value to true or pass the `--code-es5` (CLI) flag to true if you want ES5 code. - The standalone code can be generated in either CJS (module.export & require) or ESM (exports & import), it defaults to CJS. Set the `code.esm` (JS) value to true or pass the `--code-esm` (CLI) flag if you want ESM exported code. Note that the way the function is exported, differs if you are exporting a single or multiple schemas. See examples below. ### Generating function(s) using CLI In most cases you would use this functionality via [ajv-cli](https://github.com/ajv-validator/ajv-cli) (>= 4.0.0) to generate the standalone code that exports the validation function. See [ajv-cli](https://github.com/ajv-validator/ajv-cli#compile-schemas) docs and the [cli options](https://github.com/ajv-validator/ajv-cli#ajv-options) for additional information. #### Using the defaults - ES6 and CJS exports ```sh npm install -g ajv-cli ajv compile -s schema.json -o validate_schema.js ``` ### Generating using the JS library Install the package, version >= v7.0.0: ```sh npm install ajv ``` #### Generating functions(s) for a single schema using the JS library - ES6 and CJS exports ```javascript const fs = require("fs") const path = require("path") const Ajv = require("ajv") const standaloneCode = require("ajv/dist/standalone").default const schema = { $id: "https://example.com/bar.json", $schema: "http://json-schema.org/draft-07/schema#", type: "object", properties: { bar: {type: "string"}, }, "required": ["bar"] } // The generated code will have a default export: // `module.exports = ;module.exports.default = ;` const ajv = new Ajv({code: {source: true}}) const validate = ajv.compile(schema) let moduleCode = standaloneCode(ajv, validate) // Now you can write the module code to file fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode) ``` #### Generating functions(s) for multiple schemas using the JS library - ES6 and CJS exports ```javascript const fs = require("fs") const path = require("path") const Ajv = require("ajv") const standaloneCode = require("ajv/dist/standalone").default const schemaFoo = { $id: "#/definitions/Foo", $schema: "http://json-schema.org/draft-07/schema#", type: "object", properties: { foo: {"$ref": "#/definitions/Bar"} } } const schemaBar = { $id: "#/definitions/Bar", $schema: "http://json-schema.org/draft-07/schema#", type: "object", properties: { bar: {type: "string"}, }, "required": ["bar"] } // For CJS, it generates an exports array, will generate // `exports["#/definitions/Foo"] = ...;exports["#/definitions/Bar"] = ... ;` const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true}}) let moduleCode = standaloneCode(ajv) // Now you can write the module code to file fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode) ``` #### Generating functions(s) for multiple schemas using the JS library - ES6 and ESM exports ```javascript const fs = require("fs") const path = require("path") const Ajv = require("ajv") const standaloneCode = require("ajv/dist/standalone").default const schemaFoo = { $id: "#/definitions/Foo", $schema: "http://json-schema.org/draft-07/schema#", type: "object", properties: { foo: {"$ref": "#/definitions/Bar"} } } const schemaBar = { $id: "#/definitions/Bar", $schema: "http://json-schema.org/draft-07/schema#", type: "object", properties: { bar: {type: "string"}, }, "required": ["bar"] } // For ESM, the export name needs to be a valid export name, it can not be `export const #/definitions/Foo = ...;` so we // need to provide a mapping between a valid name and the $id field. Below will generate // `export const Foo = ...;export const Bar = ...;` // This mapping would not have been needed if the `$ids` was just `Bar` and `Foo` instead of `#/definitions/Foo` // and `#/definitions/Bar` respectfully const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true, esm: true}}) let moduleCode = standaloneCode(ajv, { "Foo": "#/definitions/Foo", "Bar": "#/definitions/Bar" }) // Now you can write the module code to file fs.writeFileSync(path.join(__dirname, "../consume/validate-esm.mjs"), moduleCode) ``` ::: warning ESM name mapping The ESM version only requires the mapping if the ids are not valid export names. If the $ids were just the `Foo` and `Bar` instead of `#/definitions/Foo` and `#/definitions/Bar` then the mapping would not be needed. ::: ## Using the validation function(s) ### Validating a single schemas using the JS library - ES6 and CJS ```javascript const Bar = require('./validate-cjs') const barPass = { bar: "something" } const barFail = { // bar: "something" // <= empty/omitted property that is required } let validateBar = Bar if (!validateBar(barPass)) console.log("ERRORS 1:", validateBar.errors) //Never reaches this because valid if (!validateBar(barFail)) console.log("ERRORS 2:", validateBar.errors) //Errors array gets logged ``` ### Validating multiple schemas using the JS library - ES6 and CJS ```javascript const validations = require('./validate-cjs') const fooPass = { foo: { bar: "something" } } const fooFail = { foo: { // bar: "something" // <= empty/omitted property that is required } } let validateFoo = validations["#/definitions/Foo"]; if (!validateFoo(fooPass)) console.log("ERRORS 1:", validateFoo.errors); //Never reaches this because valid if (!validateFoo(fooFail)) console.log("ERRORS 2:", validateFoo.errors); //Errors array gets logged ``` ### Validating multiple schemas using the JS library - ES6 and ESM ```javascript import {Foo, Bar} from './validate-multiple-esm.mjs'; const fooPass = { foo: { bar: "something" } } const fooFail = { foo: { // bar: "something" // bar: "something" <= empty properties } } let validateFoo = Foo; if (!validateFoo(fooPass)) console.log("ERRORS 1:", validateFoo.errors); //Never reaches here because valid if (!validateFoo(fooFail)) console.log("ERRORS 2:", validateFoo.errors); //Errors array gets logged ``` ### Requirement at runtime One of the main reason for using the standalone mode is to start applications faster to avoid runtime schema compilation. The standalone generated functions still has a dependency on the Ajv. Specifically on the code in the [runtime](https://github.com/ajv-validator/ajv/tree/master/lib/runtime) folder of the package. Completely isolated validation functions can be generated if desired (won't be for most use cases). Run the generated code through a bundler like ES Build to create completely isolated validation functions that can be imported/required without any dependency on Ajv. This is also not needed if your project is already using a bundler. ## Configuration and limitations To support standalone code generation: - Ajv option `code.source` must be set to `true` - only `code` and `macro` user-defined keywords are supported (see [User defined keywords](./keywords.md)). - when `code` keywords define variables in shared scope using `gen.scopeValue`, they must provide `code` property with the code snippet. See source code of pre-defined keywords for examples in [vocabularies folder](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies). - if formats are used in standalone code, ajv option `code.formats` should contain the code snippet that will evaluate to an object with all used format definitions - it can be a call to `require("...")` with the correct path (relative to the location of saved module): ```javascript import myFormats from "./my-formats" import Ajv, {_} from "ajv" const ajv = new Ajv({ formats: myFormats, code: { source: true, formats: _`require("./my-formats")`, }, }) If you only use formats from [ajv-formats](https://github.com/ajv-validator/ajv-formats) this option will be set by this package automatically. ================================================ FILE: docs/strict-mode.md ================================================ # Strict mode Strict mode intends to prevent any unexpected behaviours or silently ignored mistakes in user schemas. It does not change any validation results compared with the specification, but it makes some schemas invalid and throws exception or logs warning (with `strict: "log"` option) in case any restriction is violated. To disable all strict mode restrictions use option `strict: false`. Some of the restrictions can be changed with their own options [[toc]] ## JSON Type Definition schemas JTD specification is strict - whether Ajv strict mode is enabled or not it will not allow schemas with ignored or ambiguous elements, including: - unknown schema keywords - combining multiple schema forms in one schema - defining the same property as both required and optional - re-defining discriminator tag inside properties, even if the definition is non-contradictory See [JSON Type Definition](./json-type-definition.md) for informal and [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) for formal specification descriptions. The only change that strict mode introduces to JTD schemas, without changing their syntax or semantics, is the requirement that all members that are present in optional `metadata` members are defined as Ajv keywords. This restriction can be disabled with `strict: false` option, without any impact to other JTD features. ## JSON Schema schemas JSON Schema specification is very permissive and allows many elements in the schema to be quietly ignored or be ambiguous. It is recommended to use JSON Schema with strict mode. Option `strict` with value `true`/`false`/`"log"` can be used to set all strict mode restriction to be the same, otherwise individual strict mode options can be used. See [Strict mode options](./options.md#strict-mode-options). ### Prohibit ignored keywords #### Unknown keywords JSON Schema [section 6.5](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-6.5) requires to ignore unknown keywords. The motivation is to increase cross-platform portability of schemas, so that implementations that do not support certain keywords can still do partial validation. The problems with this approach are: - Different validation results with the same schema and data, leading to bugs and inconsistent behaviours. - Typos in keywords resulting in keywords being quietly ignored, requiring extensive test coverage of schemas to avoid these mistakes. By default Ajv fails schema compilation when unknown keywords are used. Users can explicitly define the keywords that should be allowed and ignored: ```javascript ajv.addKeyword("allowedKeyword") ``` or use the convenience method `addVocabulary` for multiple keywords ```javascript ajv.addVocabulary(["allowed1", "allowed2"]) // simply calls addKeyword multiple times ``` #### Ignored "additionalItems" keyword JSON Schema section [9.3.1.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2) requires to ignore "additionalItems" keyword if "items" keyword is absent or if it is not an array of items. This is inconsistent with the interaction of "additionalProperties" and "properties", and may cause unexpected results. By default Ajv fails schema compilation when "additionalItems" is used without "items" (or if "items" is not an array). #### Ignored "if", "then", "else" keywords JSON Schema section [9.2.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2) requires to ignore "if" (only annotations are collected) if both "then" and "else" are absent, and ignore "then"/"else" if "if" is absent. By default Ajv fails schema compilation in these cases. #### Ignored "contains", "maxContains" and "minContains" keywords JSON Schema sections [6.4.4, 6.4.5](https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.4) require to ignore keywords "maxContains" and "minContains" if "contains" keyword is absent. It is also implied that when "minContains" is 0 and "maxContains" is absent, "contains" keyword is always valid. By default Ajv fails schema compilation in these cases. #### Unknown formats By default unknown formats throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](./guide/combining-schemas.md#data-reference)). It is possible to opt out of format validation completely with options `validateFormats: false`. You can define all known formats with `addFormat` method or `formats` option - to have some format ignored pass `true` as its definition: ```javascript const ajv = new Ajv({formats: { reserved: true }) ``` Standard JSON Schema formats are provided in [ajv-formats](https://github.com/ajv-validator/ajv-formats) package - see [Formats](./guide/formats) section. #### Ignored defaults With `useDefaults` option Ajv modifies validated data by assigning defaults from the schema, but there are different limitations when the defaults can be ignored (see [Assigning defaults](./guide/modifying-data.md#assigning-defaults)). In strict mode Ajv fails schema compilation if such defaults are used in the schema. ### Prevent unexpected validation #### Overlap between "properties" and "patternProperties" keywords The expectation of users (see #196, #286) is that "patternProperties" only apply to properties not already defined in "properties" keyword, but JSON Schema section [9.3.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2) defines these two keywords as independent. It means that to some properties two subschemas can be applied - one defined in "properties" keyword and another defined in "patternProperties" for the pattern matching this property. By default Ajv fails schema compilation if a pattern in "patternProperties" matches a property in "properties" in the same schema. In addition to allowing such patterns by using option `strict: false` or `strictSchema: false`, there is an option `allowMatchingProperties: true` to only allow this case without disabling other strict mode restrictions - there are some rare cases when this is necessary. To reiterate, neither this nor other strict mode restrictions change the validation results - they only restrict which schemas are valid. #### Defined required properties With option `strictRequired` set to `"log"` or `true` Ajv logs warning or throws exception if the property used in "required" keyword is not defined in "properties" keyword in the same or some parent schema relating to the same object (data instance). By default this option is disabled. ::: warning Property defined in parent schema There are cases when property defined in the parent schema will not be taken into account. ::: #### Unconstrained tuples Ajv also logs a warning if "items" is an array (for schema that defines a tuple) but neither "minItems" nor "additionalItems"/"maxItems" keyword is present (or have a wrong value): ```javascript { type: "array", items: [{type: "number"}, {type: "boolean"}] } ``` The above schema may have a mistake, as tuples usually are expected to have a fixed size. To "fix" it: ```javascript { type: "array", items: [{type: "number"}, {type: "boolean"}], minItems: 2, additionalItems: false // or // maxItems: 2 } ``` Sometimes users accidentally create schema for unit (a tuple with one item) that only validates the first item, this restriction prevents this mistake as well. Use `strictTuples` option to suppress this warning (`false`) or turn it into exception (`true`). If you use `JSONSchemaType` this mistake will also be prevented on a type level. ### Strict types An additional option `strictTypes` ("log" by default) imposes additional restrictions on how type keyword is used: #### Union types With `strictTypes` option "type" keywords with multiple types (other than with "null") are prohibited. Invalid: ```javascript { type: ["string", "number"] } ``` Valid: ```javascript { type: ["object", "null"] } ``` and ```javascript { type: "object", nullable: true } ``` Unions can still be defined with `anyOf` keyword. The motivation for this restriction is that "type" is usually not the only keyword in the schema, and mixing other keywords that apply to different types is confusing. It is also consistent with wider range of versions of OpenAPI specification and has better tooling support. E.g., this example violating `strictTypes`: ```javascript { type: ["number", "array"], minimum: 0, items: { type: "number", minimum: 0 } } ``` is equivalent to this complying example, that is more verbose but also easier to maintain: ```javascript { anyOf: [ { type: "number", minimum: 0, }, { type: "array", items: { type: "number", minimum: 0, }, }, ] } ``` It also can be refactored: ```javascript { $defs: { item: { type: "number", minimum: 0 } }, anyOf: [ {$ref: "#/$defs/item"}, { type: "array", items: {$ref: "#/$defs/item"} }, ] } ``` This restriction can be lifted separately from other `strictTypes` restrictions with `allowUnionTypes: true` option. #### Contradictory types Subschemas can apply to the same data instance, and it is possible to have contradictory type keywords - it usually indicate some mistake. For example: ```javascript { type: "object", anyOf: [ {type: "array"}, {type: "object"} ] } ``` The schema above violates `strictTypes` as "array" type is not compatible with object. If you used `allowUnionTypes: true` option, the above schema can be fixed in this way: ```javascript { type: ["array", "object"], anyOf: [ {type: "array"}, {type: "object"} ] } ``` ::: tip "number" vs "integer" Type "number" can be narrowed to "integer", the opposite would violate `strictTypes`. ::: #### Require applicable types This simple JSON Schema is valid, but it violates `strictTypes`: ```javascript { properties: { foo: {type: "number"}, bar: {type: "string"} } required: ["foo", "bar"] } ``` This is a very common mistake that even people experienced with JSON Schema often make - the problem here is that any value that is not an object would be valid against this schema - this is rarely intentional. To fix it, "type" keyword has to be added: ```javascript { type: "object", properties: { foo: {type: "number"}, bar: {type: "string"} }, required: ["foo", "bar"] } ``` You do not necessarily have to have "type" keyword in the same schema object; as long as there is "type" keyword applying to the same part of data instance in the same schema document, not via "\$ref", it will be ok: ```javascript { type: "object", anyOf: [ { properties: {foo: {type: "number"}} required: ["foo"] }, { properties: {bar: {type: "string"}} required: ["bar"] } ] } ``` Both "properties" and "required" need `type: "object"` to satisfy `strictTypes` - it is sufficient to have it once in the parent schema, without repeating it in each schema. ### Strict number validation Strict mode also affects number validation. By default Ajv fails `{"type": "number"}` (or `"integer"`) validation for `Infinity` and `NaN`. ================================================ FILE: docs/testimonials.md ================================================ # What users say In the past 6 years of working on the JSON Schema Specification itself, Ajv stands out as the implementation of choice. It is very well documented and provides a rich API for extending JSON Schema which many thousands of people use in production today. A huge effort was put into testing, with many tests now forming part of the official test suite. I've personally used Ajv in production to validate requests for a federated undiagnosed genetic disease program that has lead to new scientific discoveries and literally changed lives. Ajv development can inform the future tooling and specification changes. There's no doubt that Ajv is partly responsible for the ubiquity and success of JSON Schema. [Ben Hutton](https://github.com/relequestual), JSON Schema Specification Lead
[ESLint](https://eslint.org/) has used Ajv for validating our complex configurations. Ajv has proven to be reliable over the years we’ve been using it and ESLint is proud to sponsor Ajv’s continued development. [Nicholas C. Zakas](https://github.com/nzakas), ESLint creator and TSC member
I always thought that built-in data validation is a key feature of any web framework. We decided to leverage JSON Schema in [Fastify](https://www.fastify.io), and Ajv fits our needs wonderfully: it’s fast, stable and well maintained. [Matteo Collina](https://github.com/mcollina), technical Director [@nearform](https://github.com/nearform) and TSC member
Ajv has become a centerpiece of all data-validation logic in my open-source projects and businesses. It is spec-compliant, extensible, fast and has amazing support. [Gajus Kuizinas](https://github.com/gajus), Tech / Product Founder – building [@contrahq](https://twitter.com/contrahq) ================================================ FILE: docs/v6-to-v8-migration.md ================================================ # Changes from Ajv v6.12.6 to v8.0.0 If you are migrating from v7 see [v8.0.0 release notes](https://github.com/ajv-validator/ajv/releases/tag/v8.0.0). [[toc]] ## New features - support new schema specifications: - [JSON Type Definition](./json-type-definition.md) [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). See [choosing schema language](https://ajv.js.org/guide/schema-language.html) for comparison with JSON Schema. - JSON Schema draft-2020-12: [prefixItems](./json-schema.md#prefixitems) keyword and changed semantics of [items](./json-schema.md#items-in-draft-2020-12) keyword, [dynamic recursive references](./guide/combining-schemas.md#extending-recursive-schemas). - JSON Schema draft-2019-09 features: [`unevaluatedProperties`](./json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./json-schema.md#unevaluateditems), [dynamic recursive references](./guide/combining-schemas.md#extending-recursive-schemas) and other [additional keywords](./json-schema.md#json-schema-draft-2019-09). - OpenAPI [discriminator](./json-schema.md#discriminator) keyword. - Compiled parsers (as fast as JSON.parse on valid JSON, but replace validation and fail much faster on invalid JSON) and serializers (10x+ faster than JSON.stringify) from JSON Type Definition schemas - see examples in [javascript](./guide/getting-started.html#parsing-and-serializing-json) and [typescript](./guide/typescript.html#type-safe-parsers-and-serializers). - comprehensive support for [standalone validation code](./standalone.md) - compiling one or multiple schemas to standalone modules with one or multiple exports. - to reduce the mistakes in JSON schemas and unexpected validation results, [strict mode](./strict-mode.md) is added - it prohibits ignored or ambiguous JSON Schema elements. See [Strict mode](./strict-mode.md) and [Options](./options.md) for more details. - to make code injection from untrusted schemas impossible, [code generation](./codegen.md) is fully re-written to be type-level safe against code injection. - to simplify Ajv extensions, the new keyword API that is used by pre-defined keywords is available to user-defined keywords - it is much easier to define any keywords now, especially with subschemas. - schemas are compiled to ES6 code (ES5 code generation is supported with an option). - to improve reliability and maintainability the code is migrated to TypeScript. - separate Ajv classes from draft-07, draft-2019-09, draft-2020-12 and JSON Type Definition support with different default imports (see [Getting started](./guide/getting-started.md). **Please note**: - the support for JSON-Schema draft-04 is removed - if you have schemas using "id" attributes you have to replace them with "\$id" (or continue using version 6 that will be supported until 06/30/2021). - all formats are separated to [ajv-formats](https://github.com/ajv-validator/ajv-formats) package - they have to be explicitly added if you use them. - Ajv instance can only be created with `new` keyword, as Ajv is now ES6 class. - browser bundles are automatically published to ajv-dist package (but still available on cdnjs.com). - order of schema keyword validation changed - keywords that apply to all types (allOf etc.) are now validated first, before the keywords that apply to specific data types. You can still define custom keywords that apply to all types AND are validated after type-specific keywords using option `post: true` in keyword definition. - regular expressions in keywords "pattern" and "patternProperties" are now used as if they had unicode "u" flag, as required by JSON Schema specification - it means that some regular expressions that were valid with Ajv v6 are now invalid (and vice versa). - JSON Schema validation errors changes: - `dataPath` property replaced with `instancePath` - "should" replaced with "must" in the messages - property name is removed from "propertyName" keyword error message (it is still available in `error.params.propertyName`). ## Better TypeScript support - Methods `compile` and `compileAsync` now return type-guards - see [Getting started](./guide/getting-started.md). - Method `validate` is a type-guard. - Better separation of asynchronous schemas on type level. - Schema utility types to simplify writing schemas: - JSONSchemaType\: generates the type for JSON Schema for type interface in the type parameter. - JTDSchemaType\: generates the type for JSON Type Definition schema for type interface in the type parameter. - JTDDataType\: generates the type for data given JSON Type Definition schema type in the type parameter. ## Potential migration difficulties - Schema compilation is now safer against code injections but slower than in v6 ([#1386](https://github.com/ajv-validator/ajv/issues/1386)) - consider using [standalone validation code](./standalone.md) if this is a problem. Validation performance in v8 is the same (or better, thanks to [code optimizations](./codegen.md#code-optimization)). - Schema object used as a key for compiled schema function, not serialized string ([#1413](https://github.com/ajv-validator/ajv/issues/1413)) can cause schema compilation on each validation if you pass a new schema object. See [Managing schemas](./guide/managing-schemas.md) for different approaches to manage caching of compiled validation functions. - User defined formats with standalone validation code ([#1470](https://github.com/ajv-validator/ajv/issues/1470)) require passing code snippet to [code.formats](./options.md#code) option. ## API changes - addVocabulary - NEW method that allows to add an array of keyword definitions. - addKeyword - keyword name should be passed as property in definition object, not as the first parameter (old API works with "deprecated" warning). Also "inline" keywords support is removed, code generation keywords can now be defined with "code" keyword - the same definition format that is used by all pre-defined keywords. - Ajv no longer allows to create the instance without `new` keyword (it is ES6 class). - allow `":"` in keyword names. ### Added options - strict: true/false/"log" - enables/disables all strict mode restrictions: - strictSchema: **true**/false/"log" - equivalent to the combination of strictKeywords and strictDefaults in v6, with additional restrictions (see [Strict mode](./strict-mode.md)). - strictTypes: true/false/**"log"** - prevent mistakes related to type keywords and keyword applicability (see [Strict Types](./strict-mode.md#strict-types)). - strictTuples: true/false/**"log"** - prevent incomplete tuple schemas (see [Prohibit unconstrained tuples](./strict-mode.md#prohibit-unconstrained-tuples)) - strictRequired: true/**false**/"log" - to log or fail if properties used in JSON Schema "required" are not defined in "properties" (see [Defined required properties](./strict-mode.md#defined-required-properties)). - allowUnionTypes: false - allow multiple non-null types in "type" keyword - allowMatchingProperties: false - allow overlap between "properties" and "patternProperties" keywords - discriminator: true - support OpenAPI [discriminator](./json-schema.md#discriminator) keyword - loopEnum - optimise validation of enums, similar to [loopRequired](./options.md#looprequired) option. - validateFormats - enable format validation (`true` by default) - code: {optimize: number|boolean} - control [code optimisation](./codegen.md#code-optimization) - code: {es5: true} - generate ES5 code, the default is to generate ES6 code. - code: {lines: true} - add line breaks to generated code - simplifies debugging of compiled schemas when you need it ### Changed options - `keywords` - now expects the array of keyword definitions (old API works with "deprecated" warning). - `strictNumbers` - true by default now. ### Removed options - errorDataPath - was deprecated, now removed. - format - `validateFormats: false` can be used instead, format mode can be chosen via ajv-formats package. - nullable: `nullable` keyword is supported by default. - jsonPointers: JSONPointers are used to report errors by default, `jsPropertySyntax: true` (deprecated) can be used if old format is needed. - unicode: deprecated, string length correctly accounts for multi-byte characters by default. - extendRefs: $ref siblings are validated by default (consistent with draft 2019-09), `ignoreKeywordsWithRef` (deprecated) can be used instead to ignore $ref siblings. - missingRefs: now exception is always thrown. Pass empty schema with $id that should be ignored to ajv.addSchema. - processCode: replaced with `code: {process: (code, schemaEnv: object) => string}`. - sourceCode: replaced with `code: {source: true}`. - schemaId: removed, as JSON Schema draft-04 is no longer supported. - strictDefaults, strictKeywords: it is default now, controlled with `strict` or `strictSchema`. - uniqueItems: '"uniqueItems" keyword is always validated. - unknownFormats: the same can be achieved by passing true for formats that need to be ignored via `ajv.addFormat` or `formats` option. - cache and serialize: Map is used as cache, schema object as key. ================================================ FILE: karma.conf.js ================================================ // Karma configuration // Generated on Thu Mar 13 2014 14:12:04 GMT-0700 (PDT) module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: "", // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ["mocha"], // list of files / patterns to load in the browser files: [ "bundle/ajv7.min.js", "bundle/ajv2019.min.js", "node_modules/chai/chai.js", ".browser/*.spec.js", ], // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ["dots"], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: false, // Start these browsers, currently available: // - Chrome // - ChromeCanary // - Firefox // - Opera // - Safari (only Mac) // - PhantomJS // - IE (only Windows) browsers: ["HeadlessChrome"], customLaunchers: { HeadlessChrome: { base: "ChromeHeadless", flags: ["--no-sandbox"], }, }, // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: true, browserNoActivityTimeout: 90000, }) } ================================================ FILE: lib/2019.ts ================================================ import type {AnySchemaObject} from "./types" import AjvCore, {Options} from "./core" import draft7Vocabularies from "./vocabularies/draft7" import dynamicVocabulary from "./vocabularies/dynamic" import nextVocabulary from "./vocabularies/next" import unevaluatedVocabulary from "./vocabularies/unevaluated" import discriminator from "./vocabularies/discriminator" import addMetaSchema2019 from "./refs/json-schema-2019-09" const META_SCHEMA_ID = "https://json-schema.org/draft/2019-09/schema" export class Ajv2019 extends AjvCore { constructor(opts: Options = {}) { super({ ...opts, dynamicRef: true, next: true, unevaluated: true, }) } _addVocabularies(): void { super._addVocabularies() this.addVocabulary(dynamicVocabulary) draft7Vocabularies.forEach((v) => this.addVocabulary(v)) this.addVocabulary(nextVocabulary) this.addVocabulary(unevaluatedVocabulary) if (this.opts.discriminator) this.addKeyword(discriminator) } _addDefaultMetaSchema(): void { super._addDefaultMetaSchema() const {$data, meta} = this.opts if (!meta) return addMetaSchema2019.call(this, $data) this.refs["http://json-schema.org/schema"] = META_SCHEMA_ID } defaultMeta(): string | AnySchemaObject | undefined { return (this.opts.defaultMeta = super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : undefined)) } } module.exports = exports = Ajv2019 module.exports.Ajv2019 = Ajv2019 Object.defineProperty(exports, "__esModule", {value: true}) export default Ajv2019 export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, ErrorObject, ErrorNoParams, } from "./types" export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core" export {SchemaCxt, SchemaObjCxt} from "./compile" export {KeywordCxt} from "./compile/validate" export {DefinedError} from "./vocabularies/errors" export {JSONType} from "./compile/rules" export {JSONSchemaType} from "./types/json-schema" export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen" export {default as ValidationError} from "./runtime/validation_error" export {default as MissingRefError} from "./compile/ref_error" ================================================ FILE: lib/2020.ts ================================================ import type {AnySchemaObject} from "./types" import AjvCore, {Options} from "./core" import draft2020Vocabularies from "./vocabularies/draft2020" import discriminator from "./vocabularies/discriminator" import addMetaSchema2020 from "./refs/json-schema-2020-12" const META_SCHEMA_ID = "https://json-schema.org/draft/2020-12/schema" export class Ajv2020 extends AjvCore { constructor(opts: Options = {}) { super({ ...opts, dynamicRef: true, next: true, unevaluated: true, }) } _addVocabularies(): void { super._addVocabularies() draft2020Vocabularies.forEach((v) => this.addVocabulary(v)) if (this.opts.discriminator) this.addKeyword(discriminator) } _addDefaultMetaSchema(): void { super._addDefaultMetaSchema() const {$data, meta} = this.opts if (!meta) return addMetaSchema2020.call(this, $data) this.refs["http://json-schema.org/schema"] = META_SCHEMA_ID } defaultMeta(): string | AnySchemaObject | undefined { return (this.opts.defaultMeta = super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : undefined)) } } module.exports = exports = Ajv2020 module.exports.Ajv2020 = Ajv2020 Object.defineProperty(exports, "__esModule", {value: true}) export default Ajv2020 export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, ErrorObject, ErrorNoParams, } from "./types" export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core" export {SchemaCxt, SchemaObjCxt} from "./compile" export {KeywordCxt} from "./compile/validate" export {DefinedError} from "./vocabularies/errors" export {JSONType} from "./compile/rules" export {JSONSchemaType} from "./types/json-schema" export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen" export {default as ValidationError} from "./runtime/validation_error" export {default as MissingRefError} from "./compile/ref_error" ================================================ FILE: lib/ajv.ts ================================================ import type {AnySchemaObject} from "./types" import AjvCore from "./core" import draft7Vocabularies from "./vocabularies/draft7" import discriminator from "./vocabularies/discriminator" import * as draft7MetaSchema from "./refs/json-schema-draft-07.json" const META_SUPPORT_DATA = ["/properties"] const META_SCHEMA_ID = "http://json-schema.org/draft-07/schema" export class Ajv extends AjvCore { _addVocabularies(): void { super._addVocabularies() draft7Vocabularies.forEach((v) => this.addVocabulary(v)) if (this.opts.discriminator) this.addKeyword(discriminator) } _addDefaultMetaSchema(): void { super._addDefaultMetaSchema() if (!this.opts.meta) return const metaSchema = this.opts.$data ? this.$dataMetaSchema(draft7MetaSchema, META_SUPPORT_DATA) : draft7MetaSchema this.addMetaSchema(metaSchema, META_SCHEMA_ID, false) this.refs["http://json-schema.org/schema"] = META_SCHEMA_ID } defaultMeta(): string | AnySchemaObject | undefined { return (this.opts.defaultMeta = super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : undefined)) } } module.exports = exports = Ajv module.exports.Ajv = Ajv Object.defineProperty(exports, "__esModule", {value: true}) export default Ajv export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, SchemaValidateFunction, ErrorObject, ErrorNoParams, } from "./types" export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core" export {SchemaCxt, SchemaObjCxt} from "./compile" export {KeywordCxt} from "./compile/validate" export {DefinedError} from "./vocabularies/errors" export {JSONType} from "./compile/rules" export {JSONSchemaType} from "./types/json-schema" export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen" export {default as ValidationError} from "./runtime/validation_error" export {default as MissingRefError} from "./compile/ref_error" ================================================ FILE: lib/compile/codegen/code.ts ================================================ // eslint-disable-next-line @typescript-eslint/no-extraneous-class export abstract class _CodeOrName { abstract readonly str: string abstract readonly names: UsedNames abstract toString(): string abstract emptyStr(): boolean } export const IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i export class Name extends _CodeOrName { readonly str: string constructor(s: string) { super() if (!IDENTIFIER.test(s)) throw new Error("CodeGen: name must be a valid identifier") this.str = s } toString(): string { return this.str } emptyStr(): boolean { return false } get names(): UsedNames { return {[this.str]: 1} } } export class _Code extends _CodeOrName { readonly _items: readonly CodeItem[] private _str?: string private _names?: UsedNames constructor(code: string | readonly CodeItem[]) { super() this._items = typeof code === "string" ? [code] : code } toString(): string { return this.str } emptyStr(): boolean { if (this._items.length > 1) return false const item = this._items[0] return item === "" || item === '""' } get str(): string { return (this._str ??= this._items.reduce((s: string, c: CodeItem) => `${s}${c}`, "")) } get names(): UsedNames { return (this._names ??= this._items.reduce((names: UsedNames, c) => { if (c instanceof Name) names[c.str] = (names[c.str] || 0) + 1 return names }, {})) } } export type CodeItem = Name | string | number | boolean | null export type UsedNames = Record export type Code = _Code | Name export type SafeExpr = Code | number | boolean | null export const nil = new _Code("") type CodeArg = SafeExpr | string | undefined export function _(strs: TemplateStringsArray, ...args: CodeArg[]): _Code { const code: CodeItem[] = [strs[0]] let i = 0 while (i < args.length) { addCodeArg(code, args[i]) code.push(strs[++i]) } return new _Code(code) } const plus = new _Code("+") export function str(strs: TemplateStringsArray, ...args: (CodeArg | string[])[]): _Code { const expr: CodeItem[] = [safeStringify(strs[0])] let i = 0 while (i < args.length) { expr.push(plus) addCodeArg(expr, args[i]) expr.push(plus, safeStringify(strs[++i])) } optimize(expr) return new _Code(expr) } export function addCodeArg(code: CodeItem[], arg: CodeArg | string[]): void { if (arg instanceof _Code) code.push(...arg._items) else if (arg instanceof Name) code.push(arg) else code.push(interpolate(arg)) } function optimize(expr: CodeItem[]): void { let i = 1 while (i < expr.length - 1) { if (expr[i] === plus) { const res = mergeExprItems(expr[i - 1], expr[i + 1]) if (res !== undefined) { expr.splice(i - 1, 3, res) continue } expr[i++] = "+" } i++ } } function mergeExprItems(a: CodeItem, b: CodeItem): CodeItem | undefined { if (b === '""') return a if (a === '""') return b if (typeof a == "string") { if (b instanceof Name || a[a.length - 1] !== '"') return if (typeof b != "string") return `${a.slice(0, -1)}${b}"` if (b[0] === '"') return a.slice(0, -1) + b.slice(1) return } if (typeof b == "string" && b[0] === '"' && !(a instanceof Name)) return `"${a}${b.slice(1)}` return } export function strConcat(c1: Code, c2: Code): Code { return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}` } // TODO do not allow arrays here function interpolate(x?: string | string[] | number | boolean | null): SafeExpr | string { return typeof x == "number" || typeof x == "boolean" || x === null ? x : safeStringify(Array.isArray(x) ? x.join(",") : x) } export function stringify(x: unknown): Code { return new _Code(safeStringify(x)) } export function safeStringify(x: unknown): string { return JSON.stringify(x) .replace(/\u2028/g, "\\u2028") .replace(/\u2029/g, "\\u2029") } export function getProperty(key: Code | string | number): Code { return typeof key == "string" && IDENTIFIER.test(key) ? new _Code(`.${key}`) : _`[${key}]` } //Does best effort to format the name properly export function getEsmExportName(key: Code | string | number): Code { if (typeof key == "string" && IDENTIFIER.test(key)) { return new _Code(`${key}`) } throw new Error(`CodeGen: invalid export name: ${key}, use explicit $id name mapping`) } export function regexpCode(rx: RegExp): Code { return new _Code(rx.toString()) } ================================================ FILE: lib/compile/codegen/index.ts ================================================ import type {ScopeValueSets, NameValue, ValueScope, ValueScopeName} from "./scope" import {_, nil, _Code, Code, Name, UsedNames, CodeItem, addCodeArg, _CodeOrName} from "./code" import {Scope, varKinds} from "./scope" export {_, str, strConcat, nil, getProperty, stringify, regexpCode, Name, Code} from "./code" export {Scope, ScopeStore, ValueScope, ValueScopeName, ScopeValueSets, varKinds} from "./scope" // type for expressions that can be safely inserted in code without quotes export type SafeExpr = Code | number | boolean | null // type that is either Code of function that adds code to CodeGen instance using its methods export type Block = Code | (() => void) export const operators = { GT: new _Code(">"), GTE: new _Code(">="), LT: new _Code("<"), LTE: new _Code("<="), EQ: new _Code("==="), NEQ: new _Code("!=="), NOT: new _Code("!"), OR: new _Code("||"), AND: new _Code("&&"), ADD: new _Code("+"), } abstract class Node { abstract readonly names: UsedNames optimizeNodes(): this | ChildNode | ChildNode[] | undefined { return this } optimizeNames(_names: UsedNames, _constants: Constants): this | undefined { return this } // get count(): number { // return 1 // } } class Def extends Node { constructor( private readonly varKind: Name, private readonly name: Name, private rhs?: SafeExpr ) { super() } render({es5, _n}: CGOptions): string { const varKind = es5 ? varKinds.var : this.varKind const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}` return `${varKind} ${this.name}${rhs};` + _n } optimizeNames(names: UsedNames, constants: Constants): this | undefined { if (!names[this.name.str]) return if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants) return this } get names(): UsedNames { return this.rhs instanceof _CodeOrName ? this.rhs.names : {} } } class Assign extends Node { constructor( readonly lhs: Code, public rhs: SafeExpr, private readonly sideEffects?: boolean ) { super() } render({_n}: CGOptions): string { return `${this.lhs} = ${this.rhs};` + _n } optimizeNames(names: UsedNames, constants: Constants): this | undefined { if (this.lhs instanceof Name && !names[this.lhs.str] && !this.sideEffects) return this.rhs = optimizeExpr(this.rhs, names, constants) return this } get names(): UsedNames { const names = this.lhs instanceof Name ? {} : {...this.lhs.names} return addExprNames(names, this.rhs) } } class AssignOp extends Assign { constructor( lhs: Code, private readonly op: Code, rhs: SafeExpr, sideEffects?: boolean ) { super(lhs, rhs, sideEffects) } render({_n}: CGOptions): string { return `${this.lhs} ${this.op}= ${this.rhs};` + _n } } class Label extends Node { readonly names: UsedNames = {} constructor(readonly label: Name) { super() } render({_n}: CGOptions): string { return `${this.label}:` + _n } } class Break extends Node { readonly names: UsedNames = {} constructor(readonly label?: Code) { super() } render({_n}: CGOptions): string { const label = this.label ? ` ${this.label}` : "" return `break${label};` + _n } } class Throw extends Node { constructor(readonly error: Code) { super() } render({_n}: CGOptions): string { return `throw ${this.error};` + _n } get names(): UsedNames { return this.error.names } } class AnyCode extends Node { constructor(private code: SafeExpr) { super() } render({_n}: CGOptions): string { return `${this.code};` + _n } optimizeNodes(): this | undefined { return `${this.code}` ? this : undefined } optimizeNames(names: UsedNames, constants: Constants): this { this.code = optimizeExpr(this.code, names, constants) return this } get names(): UsedNames { return this.code instanceof _CodeOrName ? this.code.names : {} } } abstract class ParentNode extends Node { constructor(readonly nodes: ChildNode[] = []) { super() } render(opts: CGOptions): string { return this.nodes.reduce((code, n) => code + n.render(opts), "") } optimizeNodes(): this | ChildNode | ChildNode[] | undefined { const {nodes} = this let i = nodes.length while (i--) { const n = nodes[i].optimizeNodes() if (Array.isArray(n)) nodes.splice(i, 1, ...n) else if (n) nodes[i] = n else nodes.splice(i, 1) } return nodes.length > 0 ? this : undefined } optimizeNames(names: UsedNames, constants: Constants): this | undefined { const {nodes} = this let i = nodes.length while (i--) { // iterating backwards improves 1-pass optimization const n = nodes[i] if (n.optimizeNames(names, constants)) continue subtractNames(names, n.names) nodes.splice(i, 1) } return nodes.length > 0 ? this : undefined } get names(): UsedNames { return this.nodes.reduce((names: UsedNames, n) => addNames(names, n.names), {}) } // get count(): number { // return this.nodes.reduce((c, n) => c + n.count, 1) // } } abstract class BlockNode extends ParentNode { render(opts: CGOptions): string { return "{" + opts._n + super.render(opts) + "}" + opts._n } } class Root extends ParentNode {} class Else extends BlockNode { static readonly kind = "else" } class If extends BlockNode { static readonly kind = "if" else?: If | Else constructor( private condition: Code | boolean, nodes?: ChildNode[] ) { super(nodes) } render(opts: CGOptions): string { let code = `if(${this.condition})` + super.render(opts) if (this.else) code += "else " + this.else.render(opts) return code } optimizeNodes(): If | ChildNode[] | undefined { super.optimizeNodes() const cond = this.condition if (cond === true) return this.nodes // else is ignored here let e = this.else if (e) { const ns = e.optimizeNodes() e = this.else = Array.isArray(ns) ? new Else(ns) : (ns as Else | undefined) } if (e) { if (cond === false) return e instanceof If ? e : e.nodes if (this.nodes.length) return this return new If(not(cond), e instanceof If ? [e] : e.nodes) } if (cond === false || !this.nodes.length) return undefined return this } optimizeNames(names: UsedNames, constants: Constants): this | undefined { this.else = this.else?.optimizeNames(names, constants) if (!(super.optimizeNames(names, constants) || this.else)) return this.condition = optimizeExpr(this.condition, names, constants) return this } get names(): UsedNames { const names = super.names addExprNames(names, this.condition) if (this.else) addNames(names, this.else.names) return names } // get count(): number { // return super.count + (this.else?.count || 0) // } } abstract class For extends BlockNode { static readonly kind = "for" } class ForLoop extends For { constructor(private iteration: Code) { super() } render(opts: CGOptions): string { return `for(${this.iteration})` + super.render(opts) } optimizeNames(names: UsedNames, constants: Constants): this | undefined { if (!super.optimizeNames(names, constants)) return this.iteration = optimizeExpr(this.iteration, names, constants) return this } get names(): UsedNames { return addNames(super.names, this.iteration.names) } } class ForRange extends For { constructor( private readonly varKind: Name, private readonly name: Name, private readonly from: SafeExpr, private readonly to: SafeExpr ) { super() } render(opts: CGOptions): string { const varKind = opts.es5 ? varKinds.var : this.varKind const {name, from, to} = this return `for(${varKind} ${name}=${from}; ${name}<${to}; ${name}++)` + super.render(opts) } get names(): UsedNames { const names = addExprNames(super.names, this.from) return addExprNames(names, this.to) } } class ForIter extends For { constructor( private readonly loop: "of" | "in", private readonly varKind: Name, private readonly name: Name, private iterable: Code ) { super() } render(opts: CGOptions): string { return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts) } optimizeNames(names: UsedNames, constants: Constants): this | undefined { if (!super.optimizeNames(names, constants)) return this.iterable = optimizeExpr(this.iterable, names, constants) return this } get names(): UsedNames { return addNames(super.names, this.iterable.names) } } class Func extends BlockNode { static readonly kind = "func" constructor( public name: Name, public args: Code, public async?: boolean ) { super() } render(opts: CGOptions): string { const _async = this.async ? "async " : "" return `${_async}function ${this.name}(${this.args})` + super.render(opts) } } class Return extends ParentNode { static readonly kind = "return" render(opts: CGOptions): string { return "return " + super.render(opts) } } class Try extends BlockNode { catch?: Catch finally?: Finally render(opts: CGOptions): string { let code = "try" + super.render(opts) if (this.catch) code += this.catch.render(opts) if (this.finally) code += this.finally.render(opts) return code } optimizeNodes(): this { super.optimizeNodes() this.catch?.optimizeNodes() as Catch | undefined this.finally?.optimizeNodes() as Finally | undefined return this } optimizeNames(names: UsedNames, constants: Constants): this { super.optimizeNames(names, constants) this.catch?.optimizeNames(names, constants) this.finally?.optimizeNames(names, constants) return this } get names(): UsedNames { const names = super.names if (this.catch) addNames(names, this.catch.names) if (this.finally) addNames(names, this.finally.names) return names } // get count(): number { // return super.count + (this.catch?.count || 0) + (this.finally?.count || 0) // } } class Catch extends BlockNode { static readonly kind = "catch" constructor(readonly error: Name) { super() } render(opts: CGOptions): string { return `catch(${this.error})` + super.render(opts) } } class Finally extends BlockNode { static readonly kind = "finally" render(opts: CGOptions): string { return "finally" + super.render(opts) } } type StartBlockNode = If | For | Func | Return | Try type LeafNode = Def | Assign | Label | Break | Throw | AnyCode type ChildNode = StartBlockNode | LeafNode type EndBlockNodeType = | typeof If | typeof Else | typeof For | typeof Func | typeof Return | typeof Catch | typeof Finally type Constants = Record export interface CodeGenOptions { es5?: boolean lines?: boolean ownProperties?: boolean } interface CGOptions extends CodeGenOptions { _n: "\n" | "" } export class CodeGen { readonly _scope: Scope readonly _extScope: ValueScope readonly _values: ScopeValueSets = {} private readonly _nodes: ParentNode[] private readonly _blockStarts: number[] = [] private readonly _constants: Constants = {} private readonly opts: CGOptions constructor(extScope: ValueScope, opts: CodeGenOptions = {}) { this.opts = {...opts, _n: opts.lines ? "\n" : ""} this._extScope = extScope this._scope = new Scope({parent: extScope}) this._nodes = [new Root()] } toString(): string { return this._root.render(this.opts) } // returns unique name in the internal scope name(prefix: string): Name { return this._scope.name(prefix) } // reserves unique name in the external scope scopeName(prefix: string): ValueScopeName { return this._extScope.name(prefix) } // reserves unique name in the external scope and assigns value to it scopeValue(prefixOrName: ValueScopeName | string, value: NameValue): Name { const name = this._extScope.value(prefixOrName, value) const vs = this._values[name.prefix] || (this._values[name.prefix] = new Set()) vs.add(name) return name } getScopeValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined { return this._extScope.getValue(prefix, keyOrRef) } // return code that assigns values in the external scope to the names that are used internally // (same names that were returned by gen.scopeName or gen.scopeValue) scopeRefs(scopeName: Name): Code { return this._extScope.scopeRefs(scopeName, this._values) } scopeCode(): Code { return this._extScope.scopeCode(this._values) } private _def( varKind: Name, nameOrPrefix: Name | string, rhs?: SafeExpr, constant?: boolean ): Name { const name = this._scope.toName(nameOrPrefix) if (rhs !== undefined && constant) this._constants[name.str] = rhs this._leafNode(new Def(varKind, name, rhs)) return name } // `const` declaration (`var` in es5 mode) const(nameOrPrefix: Name | string, rhs: SafeExpr, _constant?: boolean): Name { return this._def(varKinds.const, nameOrPrefix, rhs, _constant) } // `let` declaration with optional assignment (`var` in es5 mode) let(nameOrPrefix: Name | string, rhs?: SafeExpr, _constant?: boolean): Name { return this._def(varKinds.let, nameOrPrefix, rhs, _constant) } // `var` declaration with optional assignment var(nameOrPrefix: Name | string, rhs?: SafeExpr, _constant?: boolean): Name { return this._def(varKinds.var, nameOrPrefix, rhs, _constant) } // assignment code assign(lhs: Code, rhs: SafeExpr, sideEffects?: boolean): CodeGen { return this._leafNode(new Assign(lhs, rhs, sideEffects)) } // `+=` code add(lhs: Code, rhs: SafeExpr): CodeGen { return this._leafNode(new AssignOp(lhs, operators.ADD, rhs)) } // appends passed SafeExpr to code or executes Block code(c: Block | SafeExpr): CodeGen { if (typeof c == "function") c() else if (c !== nil) this._leafNode(new AnyCode(c)) return this } // returns code for object literal for the passed argument list of key-value pairs object(...keyValues: [Name | string, SafeExpr | string][]): _Code { const code: CodeItem[] = ["{"] for (const [key, value] of keyValues) { if (code.length > 1) code.push(",") code.push(key) if (key !== value || this.opts.es5) { code.push(":") addCodeArg(code, value) } } code.push("}") return new _Code(code) } // `if` clause (or statement if `thenBody` and, optionally, `elseBody` are passed) if(condition: Code | boolean, thenBody?: Block, elseBody?: Block): CodeGen { this._blockNode(new If(condition)) if (thenBody && elseBody) { this.code(thenBody).else().code(elseBody).endIf() } else if (thenBody) { this.code(thenBody).endIf() } else if (elseBody) { throw new Error('CodeGen: "else" body without "then" body') } return this } // `else if` clause - invalid without `if` or after `else` clauses elseIf(condition: Code | boolean): CodeGen { return this._elseNode(new If(condition)) } // `else` clause - only valid after `if` or `else if` clauses else(): CodeGen { return this._elseNode(new Else()) } // end `if` statement (needed if gen.if was used only with condition) endIf(): CodeGen { return this._endBlockNode(If, Else) } private _for(node: For, forBody?: Block): CodeGen { this._blockNode(node) if (forBody) this.code(forBody).endFor() return this } // a generic `for` clause (or statement if `forBody` is passed) for(iteration: Code, forBody?: Block): CodeGen { return this._for(new ForLoop(iteration), forBody) } // `for` statement for a range of values forRange( nameOrPrefix: Name | string, from: SafeExpr, to: SafeExpr, forBody: (index: Name) => void, varKind: Code = this.opts.es5 ? varKinds.var : varKinds.let ): CodeGen { const name = this._scope.toName(nameOrPrefix) return this._for(new ForRange(varKind, name, from, to), () => forBody(name)) } // `for-of` statement (in es5 mode replace with a normal for loop) forOf( nameOrPrefix: Name | string, iterable: Code, forBody: (item: Name) => void, varKind: Code = varKinds.const ): CodeGen { const name = this._scope.toName(nameOrPrefix) if (this.opts.es5) { const arr = iterable instanceof Name ? iterable : this.var("_arr", iterable) return this.forRange("_i", 0, _`${arr}.length`, (i) => { this.var(name, _`${arr}[${i}]`) forBody(name) }) } return this._for(new ForIter("of", varKind, name, iterable), () => forBody(name)) } // `for-in` statement. // With option `ownProperties` replaced with a `for-of` loop for object keys forIn( nameOrPrefix: Name | string, obj: Code, forBody: (item: Name) => void, varKind: Code = this.opts.es5 ? varKinds.var : varKinds.const ): CodeGen { if (this.opts.ownProperties) { return this.forOf(nameOrPrefix, _`Object.keys(${obj})`, forBody) } const name = this._scope.toName(nameOrPrefix) return this._for(new ForIter("in", varKind, name, obj), () => forBody(name)) } // end `for` loop endFor(): CodeGen { return this._endBlockNode(For) } // `label` statement label(label: Name): CodeGen { return this._leafNode(new Label(label)) } // `break` statement break(label?: Code): CodeGen { return this._leafNode(new Break(label)) } // `return` statement return(value: Block | SafeExpr): CodeGen { const node = new Return() this._blockNode(node) this.code(value) if (node.nodes.length !== 1) throw new Error('CodeGen: "return" should have one node') return this._endBlockNode(Return) } // `try` statement try(tryBody: Block, catchCode?: (e: Name) => void, finallyCode?: Block): CodeGen { if (!catchCode && !finallyCode) throw new Error('CodeGen: "try" without "catch" and "finally"') const node = new Try() this._blockNode(node) this.code(tryBody) if (catchCode) { const error = this.name("e") this._currNode = node.catch = new Catch(error) catchCode(error) } if (finallyCode) { this._currNode = node.finally = new Finally() this.code(finallyCode) } return this._endBlockNode(Catch, Finally) } // `throw` statement throw(error: Code): CodeGen { return this._leafNode(new Throw(error)) } // start self-balancing block block(body?: Block, nodeCount?: number): CodeGen { this._blockStarts.push(this._nodes.length) if (body) this.code(body).endBlock(nodeCount) return this } // end the current self-balancing block endBlock(nodeCount?: number): CodeGen { const len = this._blockStarts.pop() if (len === undefined) throw new Error("CodeGen: not in self-balancing block") const toClose = this._nodes.length - len if (toClose < 0 || (nodeCount !== undefined && toClose !== nodeCount)) { throw new Error(`CodeGen: wrong number of nodes: ${toClose} vs ${nodeCount} expected`) } this._nodes.length = len return this } // `function` heading (or definition if funcBody is passed) func(name: Name, args: Code = nil, async?: boolean, funcBody?: Block): CodeGen { this._blockNode(new Func(name, args, async)) if (funcBody) this.code(funcBody).endFunc() return this } // end function definition endFunc(): CodeGen { return this._endBlockNode(Func) } optimize(n = 1): void { while (n-- > 0) { this._root.optimizeNodes() this._root.optimizeNames(this._root.names, this._constants) } } private _leafNode(node: LeafNode): CodeGen { this._currNode.nodes.push(node) return this } private _blockNode(node: StartBlockNode): void { this._currNode.nodes.push(node) this._nodes.push(node) } private _endBlockNode(N1: EndBlockNodeType, N2?: EndBlockNodeType): CodeGen { const n = this._currNode if (n instanceof N1 || (N2 && n instanceof N2)) { this._nodes.pop() return this } throw new Error(`CodeGen: not in block "${N2 ? `${N1.kind}/${N2.kind}` : N1.kind}"`) } private _elseNode(node: If | Else): CodeGen { const n = this._currNode if (!(n instanceof If)) { throw new Error('CodeGen: "else" without "if"') } this._currNode = n.else = node return this } private get _root(): Root { return this._nodes[0] as Root } private get _currNode(): ParentNode { const ns = this._nodes return ns[ns.length - 1] } private set _currNode(node: ParentNode) { const ns = this._nodes ns[ns.length - 1] = node } // get nodeCount(): number { // return this._root.count // } } function addNames(names: UsedNames, from: UsedNames): UsedNames { for (const n in from) names[n] = (names[n] || 0) + (from[n] || 0) return names } function addExprNames(names: UsedNames, from: SafeExpr): UsedNames { return from instanceof _CodeOrName ? addNames(names, from.names) : names } function optimizeExpr(expr: T, names: UsedNames, constants: Constants): T function optimizeExpr(expr: SafeExpr, names: UsedNames, constants: Constants): SafeExpr { if (expr instanceof Name) return replaceName(expr) if (!canOptimize(expr)) return expr return new _Code( expr._items.reduce((items: CodeItem[], c: SafeExpr | string) => { if (c instanceof Name) c = replaceName(c) if (c instanceof _Code) items.push(...c._items) else items.push(c) return items }, []) ) function replaceName(n: Name): SafeExpr { const c = constants[n.str] if (c === undefined || names[n.str] !== 1) return n delete names[n.str] return c } function canOptimize(e: SafeExpr): e is _Code { return ( e instanceof _Code && e._items.some( (c) => c instanceof Name && names[c.str] === 1 && constants[c.str] !== undefined ) ) } } function subtractNames(names: UsedNames, from: UsedNames): void { for (const n in from) names[n] = (names[n] || 0) - (from[n] || 0) } export function not(x: T): T export function not(x: Code | SafeExpr): Code | SafeExpr { return typeof x == "boolean" || typeof x == "number" || x === null ? !x : _`!${par(x)}` } const andCode = mappend(operators.AND) // boolean AND (&&) expression with the passed arguments export function and(...args: Code[]): Code { return args.reduce(andCode) } const orCode = mappend(operators.OR) // boolean OR (||) expression with the passed arguments export function or(...args: Code[]): Code { return args.reduce(orCode) } type MAppend = (x: Code, y: Code) => Code function mappend(op: Code): MAppend { return (x, y) => (x === nil ? y : y === nil ? x : _`${par(x)} ${op} ${par(y)}`) } function par(x: Code): Code { return x instanceof Name ? x : _`(${x})` } ================================================ FILE: lib/compile/codegen/scope.ts ================================================ import {_, nil, Code, Name} from "./code" interface NameGroup { prefix: string index: number } export interface NameValue { ref: ValueReference // this is the reference to any value that can be referred to from generated code via `globals` var in the closure key?: unknown // any key to identify a global to avoid duplicates, if not passed ref is used code?: Code // this is the code creating the value needed for standalone code wit_out closure - can be a primitive value, function or import (`require`) } export type ValueReference = unknown // possibly make CodeGen parameterized type on this type class ValueError extends Error { readonly value?: NameValue constructor(name: ValueScopeName) { super(`CodeGen: "code" for ${name} not defined`) this.value = name.value } } interface ScopeOptions { prefixes?: Set parent?: Scope } interface ValueScopeOptions extends ScopeOptions { scope: ScopeStore es5?: boolean lines?: boolean } export type ScopeStore = Record type ScopeValues = { [Prefix in string]?: Map } export type ScopeValueSets = { [Prefix in string]?: Set } export enum UsedValueState { Started, Completed, } export type UsedScopeValues = { [Prefix in string]?: Map } export const varKinds = { const: new Name("const"), let: new Name("let"), var: new Name("var"), } export class Scope { protected readonly _names: {[Prefix in string]?: NameGroup} = {} protected readonly _prefixes?: Set protected readonly _parent?: Scope constructor({prefixes, parent}: ScopeOptions = {}) { this._prefixes = prefixes this._parent = parent } toName(nameOrPrefix: Name | string): Name { return nameOrPrefix instanceof Name ? nameOrPrefix : this.name(nameOrPrefix) } name(prefix: string): Name { return new Name(this._newName(prefix)) } protected _newName(prefix: string): string { const ng = this._names[prefix] || this._nameGroup(prefix) return `${prefix}${ng.index++}` } private _nameGroup(prefix: string): NameGroup { if (this._parent?._prefixes?.has(prefix) || (this._prefixes && !this._prefixes.has(prefix))) { throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`) } return (this._names[prefix] = {prefix, index: 0}) } } interface ScopePath { property: string itemIndex: number } export class ValueScopeName extends Name { readonly prefix: string value?: NameValue scopePath?: Code constructor(prefix: string, nameStr: string) { super(nameStr) this.prefix = prefix } setValue(value: NameValue, {property, itemIndex}: ScopePath): void { this.value = value this.scopePath = _`.${new Name(property)}[${itemIndex}]` } } interface VSOptions extends ValueScopeOptions { _n: Code } const line = _`\n` export class ValueScope extends Scope { protected readonly _values: ScopeValues = {} protected readonly _scope: ScopeStore readonly opts: VSOptions constructor(opts: ValueScopeOptions) { super(opts) this._scope = opts.scope this.opts = {...opts, _n: opts.lines ? line : nil} } get(): ScopeStore { return this._scope } name(prefix: string): ValueScopeName { return new ValueScopeName(prefix, this._newName(prefix)) } value(nameOrPrefix: ValueScopeName | string, value: NameValue): ValueScopeName { if (value.ref === undefined) throw new Error("CodeGen: ref must be passed in value") const name = this.toName(nameOrPrefix) as ValueScopeName const {prefix} = name const valueKey = value.key ?? value.ref let vs = this._values[prefix] if (vs) { const _name = vs.get(valueKey) if (_name) return _name } else { vs = this._values[prefix] = new Map() } vs.set(valueKey, name) const s = this._scope[prefix] || (this._scope[prefix] = []) const itemIndex = s.length s[itemIndex] = value.ref name.setValue(value, {property: prefix, itemIndex}) return name } getValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined { const vs = this._values[prefix] if (!vs) return return vs.get(keyOrRef) } scopeRefs(scopeName: Name, values: ScopeValues | ScopeValueSets = this._values): Code { return this._reduceValues(values, (name: ValueScopeName) => { if (name.scopePath === undefined) throw new Error(`CodeGen: name "${name}" has no value`) return _`${scopeName}${name.scopePath}` }) } scopeCode( values: ScopeValues | ScopeValueSets = this._values, usedValues?: UsedScopeValues, getCode?: (n: ValueScopeName) => Code | undefined ): Code { return this._reduceValues( values, (name: ValueScopeName) => { if (name.value === undefined) throw new Error(`CodeGen: name "${name}" has no value`) return name.value.code }, usedValues, getCode ) } private _reduceValues( values: ScopeValues | ScopeValueSets, valueCode: (n: ValueScopeName) => Code | undefined, usedValues: UsedScopeValues = {}, getCode?: (n: ValueScopeName) => Code | undefined ): Code { let code: Code = nil for (const prefix in values) { const vs = values[prefix] if (!vs) continue const nameSet = (usedValues[prefix] = usedValues[prefix] || new Map()) vs.forEach((name: ValueScopeName) => { if (nameSet.has(name)) return nameSet.set(name, UsedValueState.Started) let c = valueCode(name) if (c) { const def = this.opts.es5 ? varKinds.var : varKinds.const code = _`${code}${def} ${name} = ${c};${this.opts._n}` } else if ((c = getCode?.(name))) { code = _`${code}${c}${this.opts._n}` } else { throw new ValueError(name) } nameSet.set(name, UsedValueState.Completed) }) } return code } } ================================================ FILE: lib/compile/errors.ts ================================================ import type {KeywordErrorCxt, KeywordErrorDefinition} from "../types" import type {SchemaCxt} from "./index" import {CodeGen, _, str, strConcat, Code, Name} from "./codegen" import {SafeExpr} from "./codegen/code" import {getErrorPath, Type} from "./util" import N from "./names" export const keywordError: KeywordErrorDefinition = { message: ({keyword}) => str`must pass "${keyword}" keyword validation`, } export const keyword$DataError: KeywordErrorDefinition = { message: ({keyword, schemaType}) => schemaType ? str`"${keyword}" keyword must be ${schemaType} ($data)` : str`"${keyword}" keyword is invalid ($data)`, } export interface ErrorPaths { instancePath?: Code schemaPath?: string parentSchema?: boolean } export function reportError( cxt: KeywordErrorCxt, error: KeywordErrorDefinition = keywordError, errorPaths?: ErrorPaths, overrideAllErrors?: boolean ): void { const {it} = cxt const {gen, compositeRule, allErrors} = it const errObj = errorObjectCode(cxt, error, errorPaths) if (overrideAllErrors ?? (compositeRule || allErrors)) { addError(gen, errObj) } else { returnErrors(it, _`[${errObj}]`) } } export function reportExtraError( cxt: KeywordErrorCxt, error: KeywordErrorDefinition = keywordError, errorPaths?: ErrorPaths ): void { const {it} = cxt const {gen, compositeRule, allErrors} = it const errObj = errorObjectCode(cxt, error, errorPaths) addError(gen, errObj) if (!(compositeRule || allErrors)) { returnErrors(it, N.vErrors) } } export function resetErrorsCount(gen: CodeGen, errsCount: Name): void { gen.assign(N.errors, errsCount) gen.if(_`${N.vErrors} !== null`, () => gen.if( errsCount, () => gen.assign(_`${N.vErrors}.length`, errsCount), () => gen.assign(N.vErrors, null) ) ) } export function extendErrors({ gen, keyword, schemaValue, data, errsCount, it, }: KeywordErrorCxt): void { /* istanbul ignore if */ if (errsCount === undefined) throw new Error("ajv implementation error") const err = gen.name("err") gen.forRange("i", errsCount, N.errors, (i) => { gen.const(err, _`${N.vErrors}[${i}]`) gen.if(_`${err}.instancePath === undefined`, () => gen.assign(_`${err}.instancePath`, strConcat(N.instancePath, it.errorPath)) ) gen.assign(_`${err}.schemaPath`, str`${it.errSchemaPath}/${keyword}`) if (it.opts.verbose) { gen.assign(_`${err}.schema`, schemaValue) gen.assign(_`${err}.data`, data) } }) } function addError(gen: CodeGen, errObj: Code): void { const err = gen.const("err", errObj) gen.if( _`${N.vErrors} === null`, () => gen.assign(N.vErrors, _`[${err}]`), _`${N.vErrors}.push(${err})` ) gen.code(_`${N.errors}++`) } function returnErrors(it: SchemaCxt, errs: Code): void { const {gen, validateName, schemaEnv} = it if (schemaEnv.$async) { gen.throw(_`new ${it.ValidationError as Name}(${errs})`) } else { gen.assign(_`${validateName}.errors`, errs) gen.return(false) } } const E = { keyword: new Name("keyword"), schemaPath: new Name("schemaPath"), // also used in JTD errors params: new Name("params"), propertyName: new Name("propertyName"), message: new Name("message"), schema: new Name("schema"), parentSchema: new Name("parentSchema"), } function errorObjectCode( cxt: KeywordErrorCxt, error: KeywordErrorDefinition, errorPaths?: ErrorPaths ): Code { const {createErrors} = cxt.it if (createErrors === false) return _`{}` return errorObject(cxt, error, errorPaths) } function errorObject( cxt: KeywordErrorCxt, error: KeywordErrorDefinition, errorPaths: ErrorPaths = {} ): Code { const {gen, it} = cxt const keyValues: [Name, SafeExpr | string][] = [ errorInstancePath(it, errorPaths), errorSchemaPath(cxt, errorPaths), ] extraErrorProps(cxt, error, keyValues) return gen.object(...keyValues) } function errorInstancePath({errorPath}: SchemaCxt, {instancePath}: ErrorPaths): [Name, Code] { const instPath = instancePath ? str`${errorPath}${getErrorPath(instancePath, Type.Str)}` : errorPath return [N.instancePath, strConcat(N.instancePath, instPath)] } function errorSchemaPath( {keyword, it: {errSchemaPath}}: KeywordErrorCxt, {schemaPath, parentSchema}: ErrorPaths ): [Name, string | Code] { let schPath = parentSchema ? errSchemaPath : str`${errSchemaPath}/${keyword}` if (schemaPath) { schPath = str`${schPath}${getErrorPath(schemaPath, Type.Str)}` } return [E.schemaPath, schPath] } function extraErrorProps( cxt: KeywordErrorCxt, {params, message}: KeywordErrorDefinition, keyValues: [Name, SafeExpr | string][] ): void { const {keyword, data, schemaValue, it} = cxt const {opts, propertyName, topSchemaRef, schemaPath} = it keyValues.push( [E.keyword, keyword], [E.params, typeof params == "function" ? params(cxt) : params || _`{}`] ) if (opts.messages) { keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]) } if (opts.verbose) { keyValues.push( [E.schema, schemaValue], [E.parentSchema, _`${topSchemaRef}${schemaPath}`], [N.data, data] ) } if (propertyName) keyValues.push([E.propertyName, propertyName]) } ================================================ FILE: lib/compile/index.ts ================================================ import type { AnySchema, AnySchemaObject, AnyValidateFunction, AsyncValidateFunction, EvaluatedProperties, EvaluatedItems, } from "../types" import type Ajv from "../core" import type {InstanceOptions} from "../core" import {CodeGen, _, nil, stringify, Name, Code, ValueScopeName} from "./codegen" import ValidationError from "../runtime/validation_error" import N from "./names" import {LocalRefs, getFullPath, _getFullPath, inlineRef, normalizeId, resolveUrl} from "./resolve" import {schemaHasRulesButRef, unescapeFragment} from "./util" import {validateFunctionCode} from "./validate" import {URIComponent} from "fast-uri" import {JSONType} from "./rules" export type SchemaRefs = { [Ref in string]?: SchemaEnv | AnySchema } export interface SchemaCxt { readonly gen: CodeGen readonly allErrors?: boolean // validation mode - whether to collect all errors or break on error readonly data: Name // Name with reference to the current part of data instance readonly parentData: Name // should be used in keywords modifying data readonly parentDataProperty: Code | number // should be used in keywords modifying data readonly dataNames: Name[] readonly dataPathArr: (Code | number)[] readonly dataLevel: number // the level of the currently validated data, // it can be used to access both the property names and the data on all levels from the top. dataTypes: JSONType[] // data types applied to the current part of data instance definedProperties: Set // set of properties to keep track of for required checks readonly topSchemaRef: Code readonly validateName: Name evaluated?: Name readonly ValidationError?: Name readonly schema: AnySchema // current schema object - equal to parentSchema passed via KeywordCxt readonly schemaEnv: SchemaEnv readonly rootId: string baseId: string // the current schema base URI that should be used as the base for resolving URIs in references (\$ref) readonly schemaPath: Code // the run-time expression that evaluates to the property name of the current schema readonly errSchemaPath: string // this is actual string, should not be changed to Code readonly errorPath: Code readonly propertyName?: Name readonly compositeRule?: boolean // true indicates that the current schema is inside the compound keyword, // where failing some rule doesn't mean validation failure (`anyOf`, `oneOf`, `not`, `if`). // This flag is used to determine whether you can return validation result immediately after any error in case the option `allErrors` is not `true. // You only need to use it if you have many steps in your keywords and potentially can define multiple errors. props?: EvaluatedProperties | Name // properties evaluated by this schema - used by parent schema or assigned to validation function items?: EvaluatedItems | Name // last item evaluated by this schema - used by parent schema or assigned to validation function jtdDiscriminator?: string jtdMetadata?: boolean readonly createErrors?: boolean readonly opts: InstanceOptions // Ajv instance option. readonly self: Ajv // current Ajv instance } export interface SchemaObjCxt extends SchemaCxt { readonly schema: AnySchemaObject } interface SchemaEnvArgs { readonly schema: AnySchema readonly schemaId?: "$id" | "id" readonly root?: SchemaEnv readonly baseId?: string readonly schemaPath?: string readonly localRefs?: LocalRefs readonly meta?: boolean } export class SchemaEnv implements SchemaEnvArgs { readonly schema: AnySchema readonly schemaId?: "$id" | "id" readonly root: SchemaEnv baseId: string // TODO possibly, it should be readonly schemaPath?: string localRefs?: LocalRefs readonly meta?: boolean readonly $async?: boolean // true if the current schema is asynchronous. readonly refs: SchemaRefs = {} readonly dynamicAnchors: {[Ref in string]?: true} = {} validate?: AnyValidateFunction validateName?: ValueScopeName serialize?: (data: unknown) => string serializeName?: ValueScopeName parse?: (data: string) => unknown parseName?: ValueScopeName constructor(env: SchemaEnvArgs) { let schema: AnySchemaObject | undefined if (typeof env.schema == "object") schema = env.schema this.schema = env.schema this.schemaId = env.schemaId this.root = env.root || this this.baseId = env.baseId ?? normalizeId(schema?.[env.schemaId || "$id"]) this.schemaPath = env.schemaPath this.localRefs = env.localRefs this.meta = env.meta this.$async = schema?.$async this.refs = {} } } // let codeSize = 0 // let nodeCount = 0 // Compiles schema in SchemaEnv export function compileSchema(this: Ajv, sch: SchemaEnv): SchemaEnv { // TODO refactor - remove compilations const _sch = getCompilingSchema.call(this, sch) if (_sch) return _sch const rootId = getFullPath(this.opts.uriResolver, sch.root.baseId) // TODO if getFullPath removed 1 tests fails const {es5, lines} = this.opts.code const {ownProperties} = this.opts const gen = new CodeGen(this.scope, {es5, lines, ownProperties}) let _ValidationError if (sch.$async) { _ValidationError = gen.scopeValue("Error", { ref: ValidationError, code: _`require("ajv/dist/runtime/validation_error").default`, }) } const validateName = gen.scopeName("validate") sch.validateName = validateName const schemaCxt: SchemaCxt = { gen, allErrors: this.opts.allErrors, data: N.data, parentData: N.parentData, parentDataProperty: N.parentDataProperty, dataNames: [N.data], dataPathArr: [nil], // TODO can its length be used as dataLevel if nil is removed? dataLevel: 0, dataTypes: [], definedProperties: new Set(), topSchemaRef: gen.scopeValue( "schema", this.opts.code.source === true ? {ref: sch.schema, code: stringify(sch.schema)} : {ref: sch.schema} ), validateName, ValidationError: _ValidationError, schema: sch.schema, schemaEnv: sch, rootId, baseId: sch.baseId || rootId, schemaPath: nil, errSchemaPath: sch.schemaPath || (this.opts.jtd ? "" : "#"), errorPath: _`""`, opts: this.opts, self: this, } let sourceCode: string | undefined try { this._compilations.add(sch) validateFunctionCode(schemaCxt) gen.optimize(this.opts.code.optimize) // gen.optimize(1) const validateCode = gen.toString() sourceCode = `${gen.scopeRefs(N.scope)}return ${validateCode}` // console.log((codeSize += sourceCode.length), (nodeCount += gen.nodeCount)) if (this.opts.code.process) sourceCode = this.opts.code.process(sourceCode, sch) // console.log("\n\n\n *** \n", sourceCode) const makeValidate = new Function(`${N.self}`, `${N.scope}`, sourceCode) const validate: AnyValidateFunction = makeValidate(this, this.scope.get()) this.scope.value(validateName, {ref: validate}) validate.errors = null validate.schema = sch.schema validate.schemaEnv = sch if (sch.$async) (validate as AsyncValidateFunction).$async = true if (this.opts.code.source === true) { validate.source = {validateName, validateCode, scopeValues: gen._values} } if (this.opts.unevaluated) { const {props, items} = schemaCxt validate.evaluated = { props: props instanceof Name ? undefined : props, items: items instanceof Name ? undefined : items, dynamicProps: props instanceof Name, dynamicItems: items instanceof Name, } if (validate.source) validate.source.evaluated = stringify(validate.evaluated) } sch.validate = validate return sch } catch (e) { delete sch.validate delete sch.validateName if (sourceCode) this.logger.error("Error compiling schema, function code:", sourceCode) // console.log("\n\n\n *** \n", sourceCode, this.opts) throw e } finally { this._compilations.delete(sch) } } export function resolveRef( this: Ajv, root: SchemaEnv, baseId: string, ref: string ): AnySchema | SchemaEnv | undefined { ref = resolveUrl(this.opts.uriResolver, baseId, ref) const schOrFunc = root.refs[ref] if (schOrFunc) return schOrFunc let _sch = resolve.call(this, root, ref) if (_sch === undefined) { const schema = root.localRefs?.[ref] // TODO maybe localRefs should hold SchemaEnv const {schemaId} = this.opts if (schema) _sch = new SchemaEnv({schema, schemaId, root, baseId}) } if (_sch === undefined) return return (root.refs[ref] = inlineOrCompile.call(this, _sch)) } function inlineOrCompile(this: Ajv, sch: SchemaEnv): AnySchema | SchemaEnv { if (inlineRef(sch.schema, this.opts.inlineRefs)) return sch.schema return sch.validate ? sch : compileSchema.call(this, sch) } // Index of schema compilation in the currently compiled list export function getCompilingSchema(this: Ajv, schEnv: SchemaEnv): SchemaEnv | void { for (const sch of this._compilations) { if (sameSchemaEnv(sch, schEnv)) return sch } } function sameSchemaEnv(s1: SchemaEnv, s2: SchemaEnv): boolean { return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId } // resolve and compile the references ($ref) // TODO returns AnySchemaObject (if the schema can be inlined) or validation function function resolve( this: Ajv, root: SchemaEnv, // information about the root schema for the current schema ref: string // reference to resolve ): SchemaEnv | undefined { let sch while (typeof (sch = this.refs[ref]) == "string") ref = sch return sch || this.schemas[ref] || resolveSchema.call(this, root, ref) } // Resolve schema, its root and baseId export function resolveSchema( this: Ajv, root: SchemaEnv, // root object with properties schema, refs TODO below SchemaEnv is assigned to it ref: string // reference to resolve ): SchemaEnv | undefined { const p = this.opts.uriResolver.parse(ref) const refPath = _getFullPath(this.opts.uriResolver, p) let baseId = getFullPath(this.opts.uriResolver, root.baseId, undefined) // TODO `Object.keys(root.schema).length > 0` should not be needed - but removing breaks 2 tests if (Object.keys(root.schema).length > 0 && refPath === baseId) { return getJsonPointer.call(this, p, root) } const id = normalizeId(refPath) const schOrRef = this.refs[id] || this.schemas[id] if (typeof schOrRef == "string") { const sch = resolveSchema.call(this, root, schOrRef) if (typeof sch?.schema !== "object") return return getJsonPointer.call(this, p, sch) } if (typeof schOrRef?.schema !== "object") return if (!schOrRef.validate) compileSchema.call(this, schOrRef) if (id === normalizeId(ref)) { const {schema} = schOrRef const {schemaId} = this.opts const schId = schema[schemaId] if (schId) baseId = resolveUrl(this.opts.uriResolver, baseId, schId) return new SchemaEnv({schema, schemaId, root, baseId}) } return getJsonPointer.call(this, p, schOrRef) } const PREVENT_SCOPE_CHANGE = new Set([ "properties", "patternProperties", "enum", "dependencies", "definitions", ]) function getJsonPointer( this: Ajv, parsedRef: URIComponent, {baseId, schema, root}: SchemaEnv ): SchemaEnv | undefined { if (parsedRef.fragment?.[0] !== "/") return for (const part of parsedRef.fragment.slice(1).split("/")) { if (typeof schema === "boolean") return const partSchema = schema[unescapeFragment(part)] if (partSchema === undefined) return schema = partSchema // TODO PREVENT_SCOPE_CHANGE could be defined in keyword def? const schId = typeof schema === "object" && schema[this.opts.schemaId] if (!PREVENT_SCOPE_CHANGE.has(part) && schId) { baseId = resolveUrl(this.opts.uriResolver, baseId, schId) } } let env: SchemaEnv | undefined if (typeof schema != "boolean" && schema.$ref && !schemaHasRulesButRef(schema, this.RULES)) { const $ref = resolveUrl(this.opts.uriResolver, baseId, schema.$ref) env = resolveSchema.call(this, root, $ref) } // even though resolution failed we need to return SchemaEnv to throw exception // so that compileAsync loads missing schema. const {schemaId} = this.opts env = env || new SchemaEnv({schema, schemaId, root, baseId}) if (env.schema !== env.root.schema) return env return undefined } ================================================ FILE: lib/compile/jtd/parse.ts ================================================ import type Ajv from "../../core" import type {SchemaObject} from "../../types" import {jtdForms, JTDForm, SchemaObjectMap} from "./types" import {SchemaEnv, getCompilingSchema} from ".." import {_, str, and, or, nil, not, CodeGen, Code, Name, SafeExpr} from "../codegen" import MissingRefError from "../ref_error" import N from "../names" import {hasPropFunc} from "../../vocabularies/code" import {hasRef} from "../../vocabularies/jtd/ref" import {intRange, IntType} from "../../vocabularies/jtd/type" import {parseJson, parseJsonNumber, parseJsonString} from "../../runtime/parseJson" import {useFunc} from "../util" import validTimestamp from "../../runtime/timestamp" type GenParse = (cxt: ParseCxt) => void const genParse: {[F in JTDForm]: GenParse} = { elements: parseElements, values: parseValues, discriminator: parseDiscriminator, properties: parseProperties, optionalProperties: parseProperties, enum: parseEnum, type: parseType, ref: parseRef, } interface ParseCxt { readonly gen: CodeGen readonly self: Ajv // current Ajv instance readonly schemaEnv: SchemaEnv readonly definitions: SchemaObjectMap schema: SchemaObject data: Code parseName: Name char: Name } export default function compileParser( this: Ajv, sch: SchemaEnv, definitions: SchemaObjectMap ): SchemaEnv { const _sch = getCompilingSchema.call(this, sch) if (_sch) return _sch const {es5, lines} = this.opts.code const {ownProperties} = this.opts const gen = new CodeGen(this.scope, {es5, lines, ownProperties}) const parseName = gen.scopeName("parse") const cxt: ParseCxt = { self: this, gen, schema: sch.schema as SchemaObject, schemaEnv: sch, definitions, data: N.data, parseName, char: gen.name("c"), } let sourceCode: string | undefined try { this._compilations.add(sch) sch.parseName = parseName parserFunction(cxt) gen.optimize(this.opts.code.optimize) const parseFuncCode = gen.toString() sourceCode = `${gen.scopeRefs(N.scope)}return ${parseFuncCode}` const makeParse = new Function(`${N.scope}`, sourceCode) const parse: (json: string) => unknown = makeParse(this.scope.get()) this.scope.value(parseName, {ref: parse}) sch.parse = parse } catch (e) { if (sourceCode) this.logger.error("Error compiling parser, function code:", sourceCode) delete sch.parse delete sch.parseName throw e } finally { this._compilations.delete(sch) } return sch } const undef = _`undefined` function parserFunction(cxt: ParseCxt): void { const {gen, parseName, char} = cxt gen.func(parseName, _`${N.json}, ${N.jsonPos}, ${N.jsonPart}`, false, () => { gen.let(N.data) gen.let(char) gen.assign(_`${parseName}.message`, undef) gen.assign(_`${parseName}.position`, undef) gen.assign(N.jsonPos, _`${N.jsonPos} || 0`) gen.const(N.jsonLen, _`${N.json}.length`) parseCode(cxt) skipWhitespace(cxt) gen.if(N.jsonPart, () => { gen.assign(_`${parseName}.position`, N.jsonPos) gen.return(N.data) }) gen.if(_`${N.jsonPos} === ${N.jsonLen}`, () => gen.return(N.data)) jsonSyntaxError(cxt) }) } function parseCode(cxt: ParseCxt): void { let form: JTDForm | undefined for (const key of jtdForms) { if (key in cxt.schema) { form = key break } } if (form) parseNullable(cxt, genParse[form]) else parseEmpty(cxt) } const parseBoolean = parseBooleanToken(true, parseBooleanToken(false, jsonSyntaxError)) function parseNullable(cxt: ParseCxt, parseForm: GenParse): void { const {gen, schema, data} = cxt if (!schema.nullable) return parseForm(cxt) tryParseToken(cxt, "null", parseForm, () => gen.assign(data, null)) } function parseElements(cxt: ParseCxt): void { const {gen, schema, data} = cxt parseToken(cxt, "[") const ix = gen.let("i", 0) gen.assign(data, _`[]`) parseItems(cxt, "]", () => { const el = gen.let("el") parseCode({...cxt, schema: schema.elements, data: el}) gen.assign(_`${data}[${ix}++]`, el) }) } function parseValues(cxt: ParseCxt): void { const {gen, schema, data} = cxt parseToken(cxt, "{") gen.assign(data, _`{}`) parseItems(cxt, "}", () => parseKeyValue(cxt, schema.values)) } function parseItems(cxt: ParseCxt, endToken: string, block: () => void): void { tryParseItems(cxt, endToken, block) parseToken(cxt, endToken) } function tryParseItems(cxt: ParseCxt, endToken: string, block: () => void): void { const {gen} = cxt gen.for(_`;${N.jsonPos}<${N.jsonLen} && ${jsonSlice(1)}!==${endToken};`, () => { block() tryParseToken(cxt, ",", () => gen.break(), hasItem) }) function hasItem(): void { tryParseToken(cxt, endToken, () => {}, jsonSyntaxError) } } function parseKeyValue(cxt: ParseCxt, schema: SchemaObject): void { const {gen} = cxt const key = gen.let("key") parseString({...cxt, data: key}) parseToken(cxt, ":") parsePropertyValue(cxt, key, schema) } function parseDiscriminator(cxt: ParseCxt): void { const {gen, data, schema} = cxt const {discriminator, mapping} = schema parseToken(cxt, "{") gen.assign(data, _`{}`) const startPos = gen.const("pos", N.jsonPos) const value = gen.let("value") const tag = gen.let("tag") tryParseItems(cxt, "}", () => { const key = gen.let("key") parseString({...cxt, data: key}) parseToken(cxt, ":") gen.if( _`${key} === ${discriminator}`, () => { parseString({...cxt, data: tag}) gen.assign(_`${data}[${key}]`, tag) gen.break() }, () => parseEmpty({...cxt, data: value}) // can be discarded/skipped ) }) gen.assign(N.jsonPos, startPos) gen.if(_`${tag} === undefined`) parsingError(cxt, str`discriminator tag not found`) for (const tagValue in mapping) { gen.elseIf(_`${tag} === ${tagValue}`) parseSchemaProperties({...cxt, schema: mapping[tagValue]}, discriminator) } gen.else() parsingError(cxt, str`discriminator value not in schema`) gen.endIf() } function parseProperties(cxt: ParseCxt): void { const {gen, data} = cxt parseToken(cxt, "{") gen.assign(data, _`{}`) parseSchemaProperties(cxt) } function parseSchemaProperties(cxt: ParseCxt, discriminator?: string): void { const {gen, schema, data} = cxt const {properties, optionalProperties, additionalProperties} = schema parseItems(cxt, "}", () => { const key = gen.let("key") parseString({...cxt, data: key}) parseToken(cxt, ":") gen.if(false) parseDefinedProperty(cxt, key, properties) parseDefinedProperty(cxt, key, optionalProperties) if (discriminator) { gen.elseIf(_`${key} === ${discriminator}`) const tag = gen.let("tag") parseString({...cxt, data: tag}) // can be discarded, it is already assigned } gen.else() if (additionalProperties) { parseEmpty({...cxt, data: _`${data}[${key}]`}) } else { parsingError(cxt, str`property ${key} not allowed`) } gen.endIf() }) if (properties) { const hasProp = hasPropFunc(gen) const allProps: Code = and( ...Object.keys(properties).map((p): Code => _`${hasProp}.call(${data}, ${p})`) ) gen.if(not(allProps), () => parsingError(cxt, str`missing required properties`)) } } function parseDefinedProperty(cxt: ParseCxt, key: Name, schemas: SchemaObjectMap = {}): void { const {gen} = cxt for (const prop in schemas) { gen.elseIf(_`${key} === ${prop}`) parsePropertyValue(cxt, key, schemas[prop] as SchemaObject) } } function parsePropertyValue(cxt: ParseCxt, key: Name, schema: SchemaObject): void { parseCode({...cxt, schema, data: _`${cxt.data}[${key}]`}) } function parseType(cxt: ParseCxt): void { const {gen, schema, data, self} = cxt switch (schema.type) { case "boolean": parseBoolean(cxt) break case "string": parseString(cxt) break case "timestamp": { parseString(cxt) const vts = useFunc(gen, validTimestamp) const {allowDate, parseDate} = self.opts const notValid = allowDate ? _`!${vts}(${data}, true)` : _`!${vts}(${data})` const fail: Code = parseDate ? or(notValid, _`(${data} = new Date(${data}), false)`, _`isNaN(${data}.valueOf())`) : notValid gen.if(fail, () => parsingError(cxt, str`invalid timestamp`)) break } case "float32": case "float64": parseNumber(cxt) break default: { const t = schema.type as IntType if (!self.opts.int32range && (t === "int32" || t === "uint32")) { parseNumber(cxt, 16) // 2 ** 53 - max safe integer if (t === "uint32") { gen.if(_`${data} < 0`, () => parsingError(cxt, str`integer out of range`)) } } else { const [min, max, maxDigits] = intRange[t] parseNumber(cxt, maxDigits) gen.if(_`${data} < ${min} || ${data} > ${max}`, () => parsingError(cxt, str`integer out of range`) ) } } } } function parseString(cxt: ParseCxt): void { parseToken(cxt, '"') parseWith(cxt, parseJsonString) } function parseEnum(cxt: ParseCxt): void { const {gen, data, schema} = cxt const enumSch = schema.enum parseToken(cxt, '"') // TODO loopEnum gen.if(false) for (const value of enumSch) { const valueStr = JSON.stringify(value).slice(1) // remove starting quote gen.elseIf(_`${jsonSlice(valueStr.length)} === ${valueStr}`) gen.assign(data, str`${value}`) gen.add(N.jsonPos, valueStr.length) } gen.else() jsonSyntaxError(cxt) gen.endIf() } function parseNumber(cxt: ParseCxt, maxDigits?: number): void { const {gen} = cxt skipWhitespace(cxt) gen.if( _`"-0123456789".indexOf(${jsonSlice(1)}) < 0`, () => jsonSyntaxError(cxt), () => parseWith(cxt, parseJsonNumber, maxDigits) ) } function parseBooleanToken(bool: boolean, fail: GenParse): GenParse { return (cxt) => { const {gen, data} = cxt tryParseToken( cxt, `${bool}`, () => fail(cxt), () => gen.assign(data, bool) ) } } function parseRef(cxt: ParseCxt): void { const {gen, self, definitions, schema, schemaEnv} = cxt const {ref} = schema const refSchema = definitions[ref] if (!refSchema) throw new MissingRefError(self.opts.uriResolver, "", ref, `No definition ${ref}`) if (!hasRef(refSchema)) return parseCode({...cxt, schema: refSchema}) const {root} = schemaEnv const sch = compileParser.call(self, new SchemaEnv({schema: refSchema, root}), definitions) partialParse(cxt, getParser(gen, sch), true) } function getParser(gen: CodeGen, sch: SchemaEnv): Code { return sch.parse ? gen.scopeValue("parse", {ref: sch.parse}) : _`${gen.scopeValue("wrapper", {ref: sch})}.parse` } function parseEmpty(cxt: ParseCxt): void { parseWith(cxt, parseJson) } function parseWith(cxt: ParseCxt, parseFunc: {code: string}, args?: SafeExpr): void { partialParse(cxt, useFunc(cxt.gen, parseFunc), args) } function partialParse(cxt: ParseCxt, parseFunc: Name, args?: SafeExpr): void { const {gen, data} = cxt gen.assign(data, _`${parseFunc}(${N.json}, ${N.jsonPos}${args ? _`, ${args}` : nil})`) gen.assign(N.jsonPos, _`${parseFunc}.position`) gen.if(_`${data} === undefined`, () => parsingError(cxt, _`${parseFunc}.message`)) } function parseToken(cxt: ParseCxt, tok: string): void { tryParseToken(cxt, tok, jsonSyntaxError) } function tryParseToken(cxt: ParseCxt, tok: string, fail: GenParse, success?: GenParse): void { const {gen} = cxt const n = tok.length skipWhitespace(cxt) gen.if( _`${jsonSlice(n)} === ${tok}`, () => { gen.add(N.jsonPos, n) success?.(cxt) }, () => fail(cxt) ) } function skipWhitespace({gen, char: c}: ParseCxt): void { gen.code( _`while((${c}=${N.json}[${N.jsonPos}],${c}===" "||${c}==="\\n"||${c}==="\\r"||${c}==="\\t"))${N.jsonPos}++;` ) } function jsonSlice(len: number | Name): Code { return len === 1 ? _`${N.json}[${N.jsonPos}]` : _`${N.json}.slice(${N.jsonPos}, ${N.jsonPos}+${len})` } function jsonSyntaxError(cxt: ParseCxt): void { parsingError(cxt, _`"unexpected token " + ${N.json}[${N.jsonPos}]`) } function parsingError({gen, parseName}: ParseCxt, msg: Code): void { gen.assign(_`${parseName}.message`, msg) gen.assign(_`${parseName}.position`, N.jsonPos) gen.return(undef) } ================================================ FILE: lib/compile/jtd/serialize.ts ================================================ import type Ajv from "../../core" import type {SchemaObject} from "../../types" import {jtdForms, JTDForm, SchemaObjectMap} from "./types" import {SchemaEnv, getCompilingSchema} from ".." import {_, str, and, getProperty, CodeGen, Code, Name} from "../codegen" import MissingRefError from "../ref_error" import N from "../names" import {isOwnProperty} from "../../vocabularies/code" import {hasRef} from "../../vocabularies/jtd/ref" import {useFunc} from "../util" import quote from "../../runtime/quote" const genSerialize: {[F in JTDForm]: (cxt: SerializeCxt) => void} = { elements: serializeElements, values: serializeValues, discriminator: serializeDiscriminator, properties: serializeProperties, optionalProperties: serializeProperties, enum: serializeString, type: serializeType, ref: serializeRef, } interface SerializeCxt { readonly gen: CodeGen readonly self: Ajv // current Ajv instance readonly schemaEnv: SchemaEnv readonly definitions: SchemaObjectMap schema: SchemaObject data: Code } export default function compileSerializer( this: Ajv, sch: SchemaEnv, definitions: SchemaObjectMap ): SchemaEnv { const _sch = getCompilingSchema.call(this, sch) if (_sch) return _sch const {es5, lines} = this.opts.code const {ownProperties} = this.opts const gen = new CodeGen(this.scope, {es5, lines, ownProperties}) const serializeName = gen.scopeName("serialize") const cxt: SerializeCxt = { self: this, gen, schema: sch.schema as SchemaObject, schemaEnv: sch, definitions, data: N.data, } let sourceCode: string | undefined try { this._compilations.add(sch) sch.serializeName = serializeName gen.func(serializeName, N.data, false, () => { gen.let(N.json, str``) serializeCode(cxt) gen.return(N.json) }) gen.optimize(this.opts.code.optimize) const serializeFuncCode = gen.toString() sourceCode = `${gen.scopeRefs(N.scope)}return ${serializeFuncCode}` const makeSerialize = new Function(`${N.scope}`, sourceCode) const serialize: (data: unknown) => string = makeSerialize(this.scope.get()) this.scope.value(serializeName, {ref: serialize}) sch.serialize = serialize } catch (e) { if (sourceCode) this.logger.error("Error compiling serializer, function code:", sourceCode) delete sch.serialize delete sch.serializeName throw e } finally { this._compilations.delete(sch) } return sch } function serializeCode(cxt: SerializeCxt): void { let form: JTDForm | undefined for (const key of jtdForms) { if (key in cxt.schema) { form = key break } } serializeNullable(cxt, form ? genSerialize[form] : serializeEmpty) } function serializeNullable(cxt: SerializeCxt, serializeForm: (_cxt: SerializeCxt) => void): void { const {gen, schema, data} = cxt if (!schema.nullable) return serializeForm(cxt) gen.if( _`${data} === undefined || ${data} === null`, () => gen.add(N.json, _`"null"`), () => serializeForm(cxt) ) } function serializeElements(cxt: SerializeCxt): void { const {gen, schema, data} = cxt gen.add(N.json, str`[`) const first = gen.let("first", true) gen.forOf("el", data, (el) => { addComma(cxt, first) serializeCode({...cxt, schema: schema.elements, data: el}) }) gen.add(N.json, str`]`) } function serializeValues(cxt: SerializeCxt): void { const {gen, schema, data} = cxt gen.add(N.json, str`{`) const first = gen.let("first", true) gen.forIn("key", data, (key) => serializeKeyValue(cxt, key, schema.values, first)) gen.add(N.json, str`}`) } function serializeKeyValue(cxt: SerializeCxt, key: Name, schema: SchemaObject, first?: Name): void { const {gen, data} = cxt addComma(cxt, first) serializeString({...cxt, data: key}) gen.add(N.json, str`:`) const value = gen.const("value", _`${data}${getProperty(key)}`) serializeCode({...cxt, schema, data: value}) } function serializeDiscriminator(cxt: SerializeCxt): void { const {gen, schema, data} = cxt const {discriminator} = schema gen.add(N.json, str`{${JSON.stringify(discriminator)}:`) const tag = gen.const("tag", _`${data}${getProperty(discriminator)}`) serializeString({...cxt, data: tag}) gen.if(false) for (const tagValue in schema.mapping) { gen.elseIf(_`${tag} === ${tagValue}`) const sch = schema.mapping[tagValue] serializeSchemaProperties({...cxt, schema: sch}, discriminator) } gen.endIf() gen.add(N.json, str`}`) } function serializeProperties(cxt: SerializeCxt): void { const {gen} = cxt gen.add(N.json, str`{`) serializeSchemaProperties(cxt) gen.add(N.json, str`}`) } function serializeSchemaProperties(cxt: SerializeCxt, discriminator?: string): void { const {gen, schema, data} = cxt const {properties, optionalProperties} = schema const props = keys(properties) const optProps = keys(optionalProperties) const allProps = allProperties(props.concat(optProps)) let first = !discriminator let firstProp: Name | undefined for (const key of props) { if (first) first = false else gen.add(N.json, str`,`) serializeProperty(key, properties[key], keyValue(key)) } if (first) firstProp = gen.let("first", true) for (const key of optProps) { const value = keyValue(key) gen.if(and(_`${value} !== undefined`, isOwnProperty(gen, data, key)), () => { addComma(cxt, firstProp) serializeProperty(key, optionalProperties[key], value) }) } if (schema.additionalProperties) { gen.forIn("key", data, (key) => gen.if(isAdditional(key, allProps), () => serializeKeyValue(cxt, key, {}, firstProp)) ) } function keys(ps?: SchemaObjectMap): string[] { return ps ? Object.keys(ps) : [] } function allProperties(ps: string[]): string[] { if (discriminator) ps.push(discriminator) if (new Set(ps).size !== ps.length) { throw new Error("JTD: properties/optionalProperties/disciminator overlap") } return ps } function keyValue(key: string): Name { return gen.const("value", _`${data}${getProperty(key)}`) } function serializeProperty(key: string, propSchema: SchemaObject, value: Name): void { gen.add(N.json, str`${JSON.stringify(key)}:`) serializeCode({...cxt, schema: propSchema, data: value}) } function isAdditional(key: Name, ps: string[]): Code | true { return ps.length ? and(...ps.map((p) => _`${key} !== ${p}`)) : true } } function serializeType(cxt: SerializeCxt): void { const {gen, schema, data} = cxt switch (schema.type) { case "boolean": gen.add(N.json, _`${data} ? "true" : "false"`) break case "string": serializeString(cxt) break case "timestamp": gen.if( _`${data} instanceof Date`, () => gen.add(N.json, _`'"' + ${data}.toISOString() + '"'`), () => serializeString(cxt) ) break default: serializeNumber(cxt) } } function serializeString({gen, data}: SerializeCxt): void { gen.add(N.json, _`${useFunc(gen, quote)}(${data})`) } function serializeNumber({gen, data, self}: SerializeCxt): void { const condition = _`${data} === Infinity || ${data} === -Infinity || ${data} !== ${data}` if (self.opts.specialNumbers === undefined || self.opts.specialNumbers === "fast") { gen.add(N.json, _`"" + ${data}`) } else { // specialNumbers === "null" gen.if( condition, () => gen.add(N.json, _`null`), () => gen.add(N.json, _`"" + ${data}`) ) } } function serializeRef(cxt: SerializeCxt): void { const {gen, self, data, definitions, schema, schemaEnv} = cxt const {ref} = schema const refSchema = definitions[ref] if (!refSchema) throw new MissingRefError(self.opts.uriResolver, "", ref, `No definition ${ref}`) if (!hasRef(refSchema)) return serializeCode({...cxt, schema: refSchema}) const {root} = schemaEnv const sch = compileSerializer.call(self, new SchemaEnv({schema: refSchema, root}), definitions) gen.add(N.json, _`${getSerialize(gen, sch)}(${data})`) } function getSerialize(gen: CodeGen, sch: SchemaEnv): Code { return sch.serialize ? gen.scopeValue("serialize", {ref: sch.serialize}) : _`${gen.scopeValue("wrapper", {ref: sch})}.serialize` } function serializeEmpty({gen, data}: SerializeCxt): void { gen.add(N.json, _`JSON.stringify(${data})`) } function addComma({gen}: SerializeCxt, first?: Name): void { if (first) { gen.if( first, () => gen.assign(first, false), () => gen.add(N.json, str`,`) ) } else { gen.add(N.json, str`,`) } } ================================================ FILE: lib/compile/jtd/types.ts ================================================ import type {SchemaObject} from "../../types" export type SchemaObjectMap = {[Ref in string]?: SchemaObject} export const jtdForms = [ "elements", "values", "discriminator", "properties", "optionalProperties", "enum", "type", "ref", ] as const export type JTDForm = (typeof jtdForms)[number] ================================================ FILE: lib/compile/names.ts ================================================ import {Name} from "./codegen" const names = { // validation function arguments data: new Name("data"), // data passed to validation function // args passed from referencing schema valCxt: new Name("valCxt"), // validation/data context - should not be used directly, it is destructured to the names below instancePath: new Name("instancePath"), parentData: new Name("parentData"), parentDataProperty: new Name("parentDataProperty"), rootData: new Name("rootData"), // root data - same as the data passed to the first/top validation function dynamicAnchors: new Name("dynamicAnchors"), // used to support recursiveRef and dynamicRef // function scoped variables vErrors: new Name("vErrors"), // null or array of validation errors errors: new Name("errors"), // counter of validation errors this: new Name("this"), // "globals" self: new Name("self"), scope: new Name("scope"), // JTD serialize/parse name for JSON string and position json: new Name("json"), jsonPos: new Name("jsonPos"), jsonLen: new Name("jsonLen"), jsonPart: new Name("jsonPart"), } export default names ================================================ FILE: lib/compile/ref_error.ts ================================================ import {resolveUrl, normalizeId, getFullPath} from "./resolve" import type {UriResolver} from "../types" export default class MissingRefError extends Error { readonly missingRef: string readonly missingSchema: string constructor(resolver: UriResolver, baseId: string, ref: string, msg?: string) { super(msg || `can't resolve reference ${ref} from id ${baseId}`) this.missingRef = resolveUrl(resolver, baseId, ref) this.missingSchema = normalizeId(getFullPath(resolver, this.missingRef)) } } ================================================ FILE: lib/compile/resolve.ts ================================================ import type {AnySchema, AnySchemaObject, UriResolver} from "../types" import type Ajv from "../ajv" import type {URIComponent} from "fast-uri" import {eachItem} from "./util" import * as equal from "fast-deep-equal" import * as traverse from "json-schema-traverse" // the hash of local references inside the schema (created by getSchemaRefs), used for inline resolution export type LocalRefs = {[Ref in string]?: AnySchemaObject} // TODO refactor to use keyword definitions const SIMPLE_INLINED = new Set([ "type", "format", "pattern", "maxLength", "minLength", "maxProperties", "minProperties", "maxItems", "minItems", "maximum", "minimum", "uniqueItems", "multipleOf", "required", "enum", "const", ]) export function inlineRef(schema: AnySchema, limit: boolean | number = true): boolean { if (typeof schema == "boolean") return true if (limit === true) return !hasRef(schema) if (!limit) return false return countKeys(schema) <= limit } const REF_KEYWORDS = new Set([ "$ref", "$recursiveRef", "$recursiveAnchor", "$dynamicRef", "$dynamicAnchor", ]) function hasRef(schema: AnySchemaObject): boolean { for (const key in schema) { if (REF_KEYWORDS.has(key)) return true const sch = schema[key] if (Array.isArray(sch) && sch.some(hasRef)) return true if (typeof sch == "object" && hasRef(sch)) return true } return false } function countKeys(schema: AnySchemaObject): number { let count = 0 for (const key in schema) { if (key === "$ref") return Infinity count++ if (SIMPLE_INLINED.has(key)) continue if (typeof schema[key] == "object") { eachItem(schema[key], (sch) => (count += countKeys(sch))) } if (count === Infinity) return Infinity } return count } export function getFullPath(resolver: UriResolver, id = "", normalize?: boolean): string { if (normalize !== false) id = normalizeId(id) const p = resolver.parse(id) return _getFullPath(resolver, p) } export function _getFullPath(resolver: UriResolver, p: URIComponent): string { const serialized = resolver.serialize(p) return serialized.split("#")[0] + "#" } const TRAILING_SLASH_HASH = /#\/?$/ export function normalizeId(id: string | undefined): string { return id ? id.replace(TRAILING_SLASH_HASH, "") : "" } export function resolveUrl(resolver: UriResolver, baseId: string, id: string): string { id = normalizeId(id) return resolver.resolve(baseId, id) } const ANCHOR = /^[a-z_][-a-z0-9._]*$/i export function getSchemaRefs(this: Ajv, schema: AnySchema, baseId: string): LocalRefs { if (typeof schema == "boolean") return {} const {schemaId, uriResolver} = this.opts const schId = normalizeId(schema[schemaId] || baseId) const baseIds: {[JsonPtr in string]?: string} = {"": schId} const pathPrefix = getFullPath(uriResolver, schId, false) const localRefs: LocalRefs = {} const schemaRefs: Set = new Set() traverse(schema, {allKeys: true}, (sch, jsonPtr, _, parentJsonPtr) => { if (parentJsonPtr === undefined) return const fullPath = pathPrefix + jsonPtr let innerBaseId = baseIds[parentJsonPtr] if (typeof sch[schemaId] == "string") innerBaseId = addRef.call(this, sch[schemaId]) addAnchor.call(this, sch.$anchor) addAnchor.call(this, sch.$dynamicAnchor) baseIds[jsonPtr] = innerBaseId function addRef(this: Ajv, ref: string): string { // eslint-disable-next-line @typescript-eslint/unbound-method const _resolve = this.opts.uriResolver.resolve ref = normalizeId(innerBaseId ? _resolve(innerBaseId, ref) : ref) if (schemaRefs.has(ref)) throw ambiguos(ref) schemaRefs.add(ref) let schOrRef = this.refs[ref] if (typeof schOrRef == "string") schOrRef = this.refs[schOrRef] if (typeof schOrRef == "object") { checkAmbiguosRef(sch, schOrRef.schema, ref) } else if (ref !== normalizeId(fullPath)) { if (ref[0] === "#") { checkAmbiguosRef(sch, localRefs[ref], ref) localRefs[ref] = sch } else { this.refs[ref] = fullPath } } return ref } function addAnchor(this: Ajv, anchor: unknown): void { if (typeof anchor == "string") { if (!ANCHOR.test(anchor)) throw new Error(`invalid anchor "${anchor}"`) addRef.call(this, `#${anchor}`) } } }) return localRefs function checkAmbiguosRef(sch1: AnySchema, sch2: AnySchema | undefined, ref: string): void { if (sch2 !== undefined && !equal(sch1, sch2)) throw ambiguos(ref) } function ambiguos(ref: string): Error { return new Error(`reference "${ref}" resolves to more than one schema`) } } ================================================ FILE: lib/compile/rules.ts ================================================ import type {AddedKeywordDefinition} from "../types" const _jsonTypes = ["string", "number", "integer", "boolean", "null", "object", "array"] as const export type JSONType = (typeof _jsonTypes)[number] const jsonTypes: Set = new Set(_jsonTypes) export function isJSONType(x: unknown): x is JSONType { return typeof x == "string" && jsonTypes.has(x) } type ValidationTypes = { [K in JSONType]: boolean | RuleGroup | undefined } export interface ValidationRules { rules: RuleGroup[] post: RuleGroup all: {[Key in string]?: boolean | Rule} // rules that have to be validated keywords: {[Key in string]?: boolean} // all known keywords (superset of "all") types: ValidationTypes } export interface RuleGroup { type?: JSONType rules: Rule[] } // This interface wraps KeywordDefinition because definition can have multiple keywords export interface Rule { keyword: string definition: AddedKeywordDefinition } export function getRules(): ValidationRules { const groups: Record<"number" | "string" | "array" | "object", RuleGroup> = { number: {type: "number", rules: []}, string: {type: "string", rules: []}, array: {type: "array", rules: []}, object: {type: "object", rules: []}, } return { types: {...groups, integer: true, boolean: true, null: true}, rules: [{rules: []}, groups.number, groups.string, groups.array, groups.object], post: {rules: []}, all: {}, keywords: {}, } } ================================================ FILE: lib/compile/util.ts ================================================ import type {AnySchema, EvaluatedProperties, EvaluatedItems} from "../types" import type {SchemaCxt, SchemaObjCxt} from "." import {_, getProperty, Code, Name, CodeGen} from "./codegen" import {_Code} from "./codegen/code" import type {Rule, ValidationRules} from "./rules" // TODO refactor to use Set export function toHash(arr: T[]): {[K in T]?: true} { const hash: {[K in T]?: true} = {} for (const item of arr) hash[item] = true return hash } export function alwaysValidSchema(it: SchemaCxt, schema: AnySchema): boolean | void { if (typeof schema == "boolean") return schema if (Object.keys(schema).length === 0) return true checkUnknownRules(it, schema) return !schemaHasRules(schema, it.self.RULES.all) } export function checkUnknownRules(it: SchemaCxt, schema: AnySchema = it.schema): void { const {opts, self} = it if (!opts.strictSchema) return if (typeof schema === "boolean") return const rules = self.RULES.keywords for (const key in schema) { if (!rules[key]) checkStrictMode(it, `unknown keyword: "${key}"`) } } export function schemaHasRules( schema: AnySchema, rules: {[Key in string]?: boolean | Rule} ): boolean { if (typeof schema == "boolean") return !schema for (const key in schema) if (rules[key]) return true return false } export function schemaHasRulesButRef(schema: AnySchema, RULES: ValidationRules): boolean { if (typeof schema == "boolean") return !schema for (const key in schema) if (key !== "$ref" && RULES.all[key]) return true return false } export function schemaRefOrVal( {topSchemaRef, schemaPath}: SchemaObjCxt, schema: unknown, keyword: string, $data?: string | false ): Code | number | boolean { if (!$data) { if (typeof schema == "number" || typeof schema == "boolean") return schema if (typeof schema == "string") return _`${schema}` } return _`${topSchemaRef}${schemaPath}${getProperty(keyword)}` } export function unescapeFragment(str: string): string { return unescapeJsonPointer(decodeURIComponent(str)) } export function escapeFragment(str: string | number): string { return encodeURIComponent(escapeJsonPointer(str)) } export function escapeJsonPointer(str: string | number): string { if (typeof str == "number") return `${str}` return str.replace(/~/g, "~0").replace(/\//g, "~1") } export function unescapeJsonPointer(str: string): string { return str.replace(/~1/g, "/").replace(/~0/g, "~") } export function eachItem(xs: T | T[], f: (x: T) => void): void { if (Array.isArray(xs)) { for (const x of xs) f(x) } else { f(xs) } } type SomeEvaluated = EvaluatedProperties | EvaluatedItems type MergeEvaluatedFunc = ( gen: CodeGen, from: Name | T, to: Name | Exclude | undefined, toName?: typeof Name ) => Name | T interface MakeMergeFuncArgs { mergeNames: (gen: CodeGen, from: Name, to: Name) => void mergeToName: (gen: CodeGen, from: T, to: Name) => void mergeValues: (from: T, to: Exclude) => T resultToName: (gen: CodeGen, res?: T) => Name } function makeMergeEvaluated({ mergeNames, mergeToName, mergeValues, resultToName, }: MakeMergeFuncArgs): MergeEvaluatedFunc { return (gen, from, to, toName) => { const res = to === undefined ? from : to instanceof Name ? (from instanceof Name ? mergeNames(gen, from, to) : mergeToName(gen, from, to), to) : from instanceof Name ? (mergeToName(gen, to, from), from) : mergeValues(from, to) return toName === Name && !(res instanceof Name) ? resultToName(gen, res) : res } } interface MergeEvaluated { props: MergeEvaluatedFunc items: MergeEvaluatedFunc } export const mergeEvaluated: MergeEvaluated = { props: makeMergeEvaluated({ mergeNames: (gen, from, to) => gen.if(_`${to} !== true && ${from} !== undefined`, () => { gen.if( _`${from} === true`, () => gen.assign(to, true), () => gen.assign(to, _`${to} || {}`).code(_`Object.assign(${to}, ${from})`) ) }), mergeToName: (gen, from, to) => gen.if(_`${to} !== true`, () => { if (from === true) { gen.assign(to, true) } else { gen.assign(to, _`${to} || {}`) setEvaluated(gen, to, from) } }), mergeValues: (from, to) => (from === true ? true : {...from, ...to}), resultToName: evaluatedPropsToName, }), items: makeMergeEvaluated({ mergeNames: (gen, from, to) => gen.if(_`${to} !== true && ${from} !== undefined`, () => gen.assign(to, _`${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`) ), mergeToName: (gen, from, to) => gen.if(_`${to} !== true`, () => gen.assign(to, from === true ? true : _`${to} > ${from} ? ${to} : ${from}`) ), mergeValues: (from, to) => (from === true ? true : Math.max(from, to)), resultToName: (gen, items) => gen.var("items", items), }), } export function evaluatedPropsToName(gen: CodeGen, ps?: EvaluatedProperties): Name { if (ps === true) return gen.var("props", true) const props = gen.var("props", _`{}`) if (ps !== undefined) setEvaluated(gen, props, ps) return props } export function setEvaluated(gen: CodeGen, props: Name, ps: {[K in string]?: true}): void { Object.keys(ps).forEach((p) => gen.assign(_`${props}${getProperty(p)}`, true)) } const snippets: {[S in string]?: _Code} = {} export function useFunc(gen: CodeGen, f: {code: string}): Name { return gen.scopeValue("func", { ref: f, code: snippets[f.code] || (snippets[f.code] = new _Code(f.code)), }) } export enum Type { Num, Str, } export function getErrorPath( dataProp: Name | string | number, dataPropType?: Type, jsPropertySyntax?: boolean ): Code | string { // let path if (dataProp instanceof Name) { const isNumber = dataPropType === Type.Num return jsPropertySyntax ? isNumber ? _`"[" + ${dataProp} + "]"` : _`"['" + ${dataProp} + "']"` : isNumber ? _`"/" + ${dataProp}` : _`"/" + ${dataProp}.replace(/~/g, "~0").replace(/\\//g, "~1")` // TODO maybe use global escapePointer } return jsPropertySyntax ? getProperty(dataProp).toString() : "/" + escapeJsonPointer(dataProp) } export function checkStrictMode( it: SchemaCxt, msg: string, mode: boolean | "log" = it.opts.strictSchema ): void { if (!mode) return msg = `strict mode: ${msg}` if (mode === true) throw new Error(msg) it.self.logger.warn(msg) } ================================================ FILE: lib/compile/validate/applicability.ts ================================================ import type {AnySchemaObject} from "../../types" import type {SchemaObjCxt} from ".." import type {JSONType, RuleGroup, Rule} from "../rules" export function schemaHasRulesForType( {schema, self}: SchemaObjCxt, type: JSONType ): boolean | undefined { const group = self.RULES.types[type] return group && group !== true && shouldUseGroup(schema, group) } export function shouldUseGroup(schema: AnySchemaObject, group: RuleGroup): boolean { return group.rules.some((rule) => shouldUseRule(schema, rule)) } export function shouldUseRule(schema: AnySchemaObject, rule: Rule): boolean | undefined { return ( schema[rule.keyword] !== undefined || rule.definition.implements?.some((kwd) => schema[kwd] !== undefined) ) } ================================================ FILE: lib/compile/validate/boolSchema.ts ================================================ import type {KeywordErrorDefinition, KeywordErrorCxt} from "../../types" import type {SchemaCxt} from ".." import {reportError} from "../errors" import {_, Name} from "../codegen" import N from "../names" const boolError: KeywordErrorDefinition = { message: "boolean schema is false", } export function topBoolOrEmptySchema(it: SchemaCxt): void { const {gen, schema, validateName} = it if (schema === false) { falseSchemaError(it, false) } else if (typeof schema == "object" && schema.$async === true) { gen.return(N.data) } else { gen.assign(_`${validateName}.errors`, null) gen.return(true) } } export function boolOrEmptySchema(it: SchemaCxt, valid: Name): void { const {gen, schema} = it if (schema === false) { gen.var(valid, false) // TODO var falseSchemaError(it) } else { gen.var(valid, true) // TODO var } } function falseSchemaError(it: SchemaCxt, overrideAllErrors?: boolean): void { const {gen, data} = it // TODO maybe some other interface should be used for non-keyword validation errors... const cxt: KeywordErrorCxt = { gen, keyword: "false schema", data, schema: false, schemaCode: false, schemaValue: false, params: {}, it, } reportError(cxt, boolError, undefined, overrideAllErrors) } ================================================ FILE: lib/compile/validate/dataType.ts ================================================ import type { KeywordErrorDefinition, KeywordErrorCxt, ErrorObject, AnySchemaObject, } from "../../types" import type {SchemaObjCxt} from ".." import {isJSONType, JSONType} from "../rules" import {schemaHasRulesForType} from "./applicability" import {reportError} from "../errors" import {_, nil, and, not, operators, Code, Name} from "../codegen" import {toHash, schemaRefOrVal} from "../util" export enum DataType { Correct, Wrong, } export function getSchemaTypes(schema: AnySchemaObject): JSONType[] { const types = getJSONTypes(schema.type) const hasNull = types.includes("null") if (hasNull) { if (schema.nullable === false) throw new Error("type: null contradicts nullable: false") } else { if (!types.length && schema.nullable !== undefined) { throw new Error('"nullable" cannot be used without "type"') } if (schema.nullable === true) types.push("null") } return types } // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents export function getJSONTypes(ts: unknown | unknown[]): JSONType[] { const types: unknown[] = Array.isArray(ts) ? ts : ts ? [ts] : [] if (types.every(isJSONType)) return types throw new Error("type must be JSONType or JSONType[]: " + types.join(",")) } export function coerceAndCheckDataType(it: SchemaObjCxt, types: JSONType[]): boolean { const {gen, data, opts} = it const coerceTo = coerceToTypes(types, opts.coerceTypes) const checkTypes = types.length > 0 && !(coerceTo.length === 0 && types.length === 1 && schemaHasRulesForType(it, types[0])) if (checkTypes) { const wrongType = checkDataTypes(types, data, opts.strictNumbers, DataType.Wrong) gen.if(wrongType, () => { if (coerceTo.length) coerceData(it, types, coerceTo) else reportTypeError(it) }) } return checkTypes } const COERCIBLE: Set = new Set(["string", "number", "integer", "boolean", "null"]) function coerceToTypes(types: JSONType[], coerceTypes?: boolean | "array"): JSONType[] { return coerceTypes ? types.filter((t) => COERCIBLE.has(t) || (coerceTypes === "array" && t === "array")) : [] } function coerceData(it: SchemaObjCxt, types: JSONType[], coerceTo: JSONType[]): void { const {gen, data, opts} = it const dataType = gen.let("dataType", _`typeof ${data}`) const coerced = gen.let("coerced", _`undefined`) if (opts.coerceTypes === "array") { gen.if(_`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () => gen .assign(data, _`${data}[0]`) .assign(dataType, _`typeof ${data}`) .if(checkDataTypes(types, data, opts.strictNumbers), () => gen.assign(coerced, data)) ) } gen.if(_`${coerced} !== undefined`) for (const t of coerceTo) { if (COERCIBLE.has(t) || (t === "array" && opts.coerceTypes === "array")) { coerceSpecificType(t) } } gen.else() reportTypeError(it) gen.endIf() gen.if(_`${coerced} !== undefined`, () => { gen.assign(data, coerced) assignParentData(it, coerced) }) function coerceSpecificType(t: string): void { switch (t) { case "string": gen .elseIf(_`${dataType} == "number" || ${dataType} == "boolean"`) .assign(coerced, _`"" + ${data}`) .elseIf(_`${data} === null`) .assign(coerced, _`""`) return case "number": gen .elseIf( _`${dataType} == "boolean" || ${data} === null || (${dataType} == "string" && ${data} && ${data} == +${data})` ) .assign(coerced, _`+${data}`) return case "integer": gen .elseIf( _`${dataType} === "boolean" || ${data} === null || (${dataType} === "string" && ${data} && ${data} == +${data} && !(${data} % 1))` ) .assign(coerced, _`+${data}`) return case "boolean": gen .elseIf(_`${data} === "false" || ${data} === 0 || ${data} === null`) .assign(coerced, false) .elseIf(_`${data} === "true" || ${data} === 1`) .assign(coerced, true) return case "null": gen.elseIf(_`${data} === "" || ${data} === 0 || ${data} === false`) gen.assign(coerced, null) return case "array": gen .elseIf( _`${dataType} === "string" || ${dataType} === "number" || ${dataType} === "boolean" || ${data} === null` ) .assign(coerced, _`[${data}]`) } } } function assignParentData({gen, parentData, parentDataProperty}: SchemaObjCxt, expr: Name): void { // TODO use gen.property gen.if(_`${parentData} !== undefined`, () => gen.assign(_`${parentData}[${parentDataProperty}]`, expr) ) } export function checkDataType( dataType: JSONType, data: Name, strictNums?: boolean | "log", correct = DataType.Correct ): Code { const EQ = correct === DataType.Correct ? operators.EQ : operators.NEQ let cond: Code switch (dataType) { case "null": return _`${data} ${EQ} null` case "array": cond = _`Array.isArray(${data})` break case "object": cond = _`${data} && typeof ${data} == "object" && !Array.isArray(${data})` break case "integer": cond = numCond(_`!(${data} % 1) && !isNaN(${data})`) break case "number": cond = numCond() break default: return _`typeof ${data} ${EQ} ${dataType}` } return correct === DataType.Correct ? cond : not(cond) function numCond(_cond: Code = nil): Code { return and(_`typeof ${data} == "number"`, _cond, strictNums ? _`isFinite(${data})` : nil) } } export function checkDataTypes( dataTypes: JSONType[], data: Name, strictNums?: boolean | "log", correct?: DataType ): Code { if (dataTypes.length === 1) { return checkDataType(dataTypes[0], data, strictNums, correct) } let cond: Code const types = toHash(dataTypes) if (types.array && types.object) { const notObj = _`typeof ${data} != "object"` cond = types.null ? notObj : _`!${data} || ${notObj}` delete types.null delete types.array delete types.object } else { cond = nil } if (types.number) delete types.integer for (const t in types) cond = and(cond, checkDataType(t as JSONType, data, strictNums, correct)) return cond } export type TypeError = ErrorObject<"type", {type: string}> const typeError: KeywordErrorDefinition = { message: ({schema}) => `must be ${schema}`, params: ({schema, schemaValue}) => typeof schema == "string" ? _`{type: ${schema}}` : _`{type: ${schemaValue}}`, } export function reportTypeError(it: SchemaObjCxt): void { const cxt = getTypeErrorContext(it) reportError(cxt, typeError) } function getTypeErrorContext(it: SchemaObjCxt): KeywordErrorCxt { const {gen, data, schema} = it const schemaCode = schemaRefOrVal(it, schema, "type") return { gen, keyword: "type", data, schema: schema.type, schemaCode, schemaValue: schemaCode, parentSchema: schema, params: {}, it, } } ================================================ FILE: lib/compile/validate/defaults.ts ================================================ import type {SchemaObjCxt} from ".." import {_, getProperty, stringify} from "../codegen" import {checkStrictMode} from "../util" export function assignDefaults(it: SchemaObjCxt, ty?: string): void { const {properties, items} = it.schema if (ty === "object" && properties) { for (const key in properties) { assignDefault(it, key, properties[key].default) } } else if (ty === "array" && Array.isArray(items)) { items.forEach((sch, i: number) => assignDefault(it, i, sch.default)) } } function assignDefault(it: SchemaObjCxt, prop: string | number, defaultValue: unknown): void { const {gen, compositeRule, data, opts} = it if (defaultValue === undefined) return const childData = _`${data}${getProperty(prop)}` if (compositeRule) { checkStrictMode(it, `default is ignored for: ${childData}`) return } let condition = _`${childData} === undefined` if (opts.useDefaults === "empty") { condition = _`${condition} || ${childData} === null || ${childData} === ""` } // `${childData} === undefined` + // (opts.useDefaults === "empty" ? ` || ${childData} === null || ${childData} === ""` : "") gen.if(condition, _`${childData} = ${stringify(defaultValue)}`) } ================================================ FILE: lib/compile/validate/index.ts ================================================ import type { AddedKeywordDefinition, AnySchema, AnySchemaObject, KeywordErrorCxt, KeywordCxtParams, } from "../../types" import type {SchemaCxt, SchemaObjCxt} from ".." import type {InstanceOptions} from "../../core" import {boolOrEmptySchema, topBoolOrEmptySchema} from "./boolSchema" import {coerceAndCheckDataType, getSchemaTypes} from "./dataType" import {shouldUseGroup, shouldUseRule} from "./applicability" import {checkDataType, checkDataTypes, reportTypeError, DataType} from "./dataType" import {assignDefaults} from "./defaults" import {funcKeywordCode, macroKeywordCode, validateKeywordUsage, validSchemaType} from "./keyword" import {getSubschema, extendSubschemaData, SubschemaArgs, extendSubschemaMode} from "./subschema" import {_, nil, str, or, not, getProperty, Block, Code, Name, CodeGen} from "../codegen" import N from "../names" import {resolveUrl} from "../resolve" import { schemaRefOrVal, schemaHasRulesButRef, checkUnknownRules, checkStrictMode, unescapeJsonPointer, mergeEvaluated, } from "../util" import type {JSONType, Rule, RuleGroup} from "../rules" import { ErrorPaths, reportError, reportExtraError, resetErrorsCount, keyword$DataError, } from "../errors" // schema compilation - generates validation function, subschemaCode (below) is used for subschemas export function validateFunctionCode(it: SchemaCxt): void { if (isSchemaObj(it)) { checkKeywords(it) if (schemaCxtHasRules(it)) { topSchemaObjCode(it) return } } validateFunction(it, () => topBoolOrEmptySchema(it)) } function validateFunction( {gen, validateName, schema, schemaEnv, opts}: SchemaCxt, body: Block ): void { if (opts.code.es5) { gen.func(validateName, _`${N.data}, ${N.valCxt}`, schemaEnv.$async, () => { gen.code(_`"use strict"; ${funcSourceUrl(schema, opts)}`) destructureValCxtES5(gen, opts) gen.code(body) }) } else { gen.func(validateName, _`${N.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body) ) } } function destructureValCxt(opts: InstanceOptions): Code { return _`{${N.instancePath}="", ${N.parentData}, ${N.parentDataProperty}, ${N.rootData}=${ N.data }${opts.dynamicRef ? _`, ${N.dynamicAnchors}={}` : nil}}={}` } function destructureValCxtES5(gen: CodeGen, opts: InstanceOptions): void { gen.if( N.valCxt, () => { gen.var(N.instancePath, _`${N.valCxt}.${N.instancePath}`) gen.var(N.parentData, _`${N.valCxt}.${N.parentData}`) gen.var(N.parentDataProperty, _`${N.valCxt}.${N.parentDataProperty}`) gen.var(N.rootData, _`${N.valCxt}.${N.rootData}`) if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`${N.valCxt}.${N.dynamicAnchors}`) }, () => { gen.var(N.instancePath, _`""`) gen.var(N.parentData, _`undefined`) gen.var(N.parentDataProperty, _`undefined`) gen.var(N.rootData, N.data) if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`{}`) } ) } function topSchemaObjCode(it: SchemaObjCxt): void { const {schema, opts, gen} = it validateFunction(it, () => { if (opts.$comment && schema.$comment) commentKeyword(it) checkNoDefault(it) gen.let(N.vErrors, null) gen.let(N.errors, 0) if (opts.unevaluated) resetEvaluated(it) typeAndKeywords(it) returnResults(it) }) return } function resetEvaluated(it: SchemaObjCxt): void { // TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated const {gen, validateName} = it it.evaluated = gen.const("evaluated", _`${validateName}.evaluated`) gen.if(_`${it.evaluated}.dynamicProps`, () => gen.assign(_`${it.evaluated}.props`, _`undefined`)) gen.if(_`${it.evaluated}.dynamicItems`, () => gen.assign(_`${it.evaluated}.items`, _`undefined`)) } function funcSourceUrl(schema: AnySchema, opts: InstanceOptions): Code { const schId = typeof schema == "object" && schema[opts.schemaId] return schId && (opts.code.source || opts.code.process) ? _`/*# sourceURL=${schId} */` : nil } // schema compilation - this function is used recursively to generate code for sub-schemas function subschemaCode(it: SchemaCxt, valid: Name): void { if (isSchemaObj(it)) { checkKeywords(it) if (schemaCxtHasRules(it)) { subSchemaObjCode(it, valid) return } } boolOrEmptySchema(it, valid) } function schemaCxtHasRules({schema, self}: SchemaCxt): boolean { if (typeof schema == "boolean") return !schema for (const key in schema) if (self.RULES.all[key]) return true return false } function isSchemaObj(it: SchemaCxt): it is SchemaObjCxt { return typeof it.schema != "boolean" } function subSchemaObjCode(it: SchemaObjCxt, valid: Name): void { const {schema, gen, opts} = it if (opts.$comment && schema.$comment) commentKeyword(it) updateContext(it) checkAsyncSchema(it) const errsCount = gen.const("_errs", N.errors) typeAndKeywords(it, errsCount) // TODO var gen.var(valid, _`${errsCount} === ${N.errors}`) } function checkKeywords(it: SchemaObjCxt): void { checkUnknownRules(it) checkRefsAndKeywords(it) } function typeAndKeywords(it: SchemaObjCxt, errsCount?: Name): void { if (it.opts.jtd) return schemaKeywords(it, [], false, errsCount) const types = getSchemaTypes(it.schema) const checkedTypes = coerceAndCheckDataType(it, types) schemaKeywords(it, types, !checkedTypes, errsCount) } function checkRefsAndKeywords(it: SchemaObjCxt): void { const {schema, errSchemaPath, opts, self} = it if (schema.$ref && opts.ignoreKeywordsWithRef && schemaHasRulesButRef(schema, self.RULES)) { self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`) } } function checkNoDefault(it: SchemaObjCxt): void { const {schema, opts} = it if (schema.default !== undefined && opts.useDefaults && opts.strictSchema) { checkStrictMode(it, "default is ignored in the schema root") } } function updateContext(it: SchemaObjCxt): void { const schId = it.schema[it.opts.schemaId] if (schId) it.baseId = resolveUrl(it.opts.uriResolver, it.baseId, schId) } function checkAsyncSchema(it: SchemaObjCxt): void { if (it.schema.$async && !it.schemaEnv.$async) throw new Error("async schema in sync schema") } function commentKeyword({gen, schemaEnv, schema, errSchemaPath, opts}: SchemaObjCxt): void { const msg = schema.$comment if (opts.$comment === true) { gen.code(_`${N.self}.logger.log(${msg})`) } else if (typeof opts.$comment == "function") { const schemaPath = str`${errSchemaPath}/$comment` const rootName = gen.scopeValue("root", {ref: schemaEnv.root}) gen.code(_`${N.self}.opts.$comment(${msg}, ${schemaPath}, ${rootName}.schema)`) } } function returnResults(it: SchemaCxt): void { const {gen, schemaEnv, validateName, ValidationError, opts} = it if (schemaEnv.$async) { // TODO assign unevaluated gen.if( _`${N.errors} === 0`, () => gen.return(N.data), () => gen.throw(_`new ${ValidationError as Name}(${N.vErrors})`) ) } else { gen.assign(_`${validateName}.errors`, N.vErrors) if (opts.unevaluated) assignEvaluated(it) gen.return(_`${N.errors} === 0`) } } function assignEvaluated({gen, evaluated, props, items}: SchemaCxt): void { if (props instanceof Name) gen.assign(_`${evaluated}.props`, props) if (items instanceof Name) gen.assign(_`${evaluated}.items`, items) } function schemaKeywords( it: SchemaObjCxt, types: JSONType[], typeErrors: boolean, errsCount?: Name ): void { const {gen, schema, data, allErrors, opts, self} = it const {RULES} = self if (schema.$ref && (opts.ignoreKeywordsWithRef || !schemaHasRulesButRef(schema, RULES))) { gen.block(() => keywordCode(it, "$ref", (RULES.all.$ref as Rule).definition)) // TODO typecast return } if (!opts.jtd) checkStrictTypes(it, types) gen.block(() => { for (const group of RULES.rules) groupKeywords(group) groupKeywords(RULES.post) }) function groupKeywords(group: RuleGroup): void { if (!shouldUseGroup(schema, group)) return if (group.type) { gen.if(checkDataType(group.type, data, opts.strictNumbers)) iterateKeywords(it, group) if (types.length === 1 && types[0] === group.type && typeErrors) { gen.else() reportTypeError(it) } gen.endIf() } else { iterateKeywords(it, group) } // TODO make it "ok" call? if (!allErrors) gen.if(_`${N.errors} === ${errsCount || 0}`) } } function iterateKeywords(it: SchemaObjCxt, group: RuleGroup): void { const { gen, schema, opts: {useDefaults}, } = it if (useDefaults) assignDefaults(it, group.type) gen.block(() => { for (const rule of group.rules) { if (shouldUseRule(schema, rule)) { keywordCode(it, rule.keyword, rule.definition, group.type) } } }) } function checkStrictTypes(it: SchemaObjCxt, types: JSONType[]): void { if (it.schemaEnv.meta || !it.opts.strictTypes) return checkContextTypes(it, types) if (!it.opts.allowUnionTypes) checkMultipleTypes(it, types) checkKeywordTypes(it, it.dataTypes) } function checkContextTypes(it: SchemaObjCxt, types: JSONType[]): void { if (!types.length) return if (!it.dataTypes.length) { it.dataTypes = types return } types.forEach((t) => { if (!includesType(it.dataTypes, t)) { strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`) } }) narrowSchemaTypes(it, types) } function checkMultipleTypes(it: SchemaObjCxt, ts: JSONType[]): void { if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) { strictTypesError(it, "use allowUnionTypes to allow union type keyword") } } function checkKeywordTypes(it: SchemaObjCxt, ts: JSONType[]): void { const rules = it.self.RULES.all for (const keyword in rules) { const rule = rules[keyword] if (typeof rule == "object" && shouldUseRule(it.schema, rule)) { const {type} = rule.definition if (type.length && !type.some((t) => hasApplicableType(ts, t))) { strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`) } } } } function hasApplicableType(schTs: JSONType[], kwdT: JSONType): boolean { return schTs.includes(kwdT) || (kwdT === "number" && schTs.includes("integer")) } function includesType(ts: JSONType[], t: JSONType): boolean { return ts.includes(t) || (t === "integer" && ts.includes("number")) } function narrowSchemaTypes(it: SchemaObjCxt, withTypes: JSONType[]): void { const ts: JSONType[] = [] for (const t of it.dataTypes) { if (includesType(withTypes, t)) ts.push(t) else if (withTypes.includes("integer") && t === "number") ts.push("integer") } it.dataTypes = ts } function strictTypesError(it: SchemaObjCxt, msg: string): void { const schemaPath = it.schemaEnv.baseId + it.errSchemaPath msg += ` at "${schemaPath}" (strictTypes)` checkStrictMode(it, msg, it.opts.strictTypes) } export class KeywordCxt implements KeywordErrorCxt { readonly gen: CodeGen readonly allErrors?: boolean readonly keyword: string readonly data: Name // Name referencing the current level of the data instance readonly $data?: string | false schema: any // keyword value in the schema readonly schemaValue: Code | number | boolean // Code reference to keyword schema value or primitive value readonly schemaCode: Code | number | boolean // Code reference to resolved schema value (different if schema is $data) readonly schemaType: JSONType[] // allowed type(s) of keyword value in the schema readonly parentSchema: AnySchemaObject readonly errsCount?: Name // Name reference to the number of validation errors collected before this keyword, // requires option trackErrors in keyword definition params: KeywordCxtParams // object to pass parameters to error messages from keyword code readonly it: SchemaObjCxt // schema compilation context (schema is guaranteed to be an object, not boolean) readonly def: AddedKeywordDefinition constructor(it: SchemaObjCxt, def: AddedKeywordDefinition, keyword: string) { validateKeywordUsage(it, def, keyword) this.gen = it.gen this.allErrors = it.allErrors this.keyword = keyword this.data = it.data this.schema = it.schema[keyword] this.$data = def.$data && it.opts.$data && this.schema && this.schema.$data this.schemaValue = schemaRefOrVal(it, this.schema, keyword, this.$data) this.schemaType = def.schemaType this.parentSchema = it.schema this.params = {} this.it = it this.def = def if (this.$data) { this.schemaCode = it.gen.const("vSchema", getData(this.$data, it)) } else { this.schemaCode = this.schemaValue if (!validSchemaType(this.schema, def.schemaType, def.allowUndefined)) { throw new Error(`${keyword} value must be ${JSON.stringify(def.schemaType)}`) } } if ("code" in def ? def.trackErrors : def.errors !== false) { this.errsCount = it.gen.const("_errs", N.errors) } } result(condition: Code, successAction?: () => void, failAction?: () => void): void { this.failResult(not(condition), successAction, failAction) } failResult(condition: Code, successAction?: () => void, failAction?: () => void): void { this.gen.if(condition) if (failAction) failAction() else this.error() if (successAction) { this.gen.else() successAction() if (this.allErrors) this.gen.endIf() } else { if (this.allErrors) this.gen.endIf() else this.gen.else() } } pass(condition: Code, failAction?: () => void): void { this.failResult(not(condition), undefined, failAction) } fail(condition?: Code): void { if (condition === undefined) { this.error() if (!this.allErrors) this.gen.if(false) // this branch will be removed by gen.optimize return } this.gen.if(condition) this.error() if (this.allErrors) this.gen.endIf() else this.gen.else() } fail$data(condition: Code): void { if (!this.$data) return this.fail(condition) const {schemaCode} = this this.fail(_`${schemaCode} !== undefined && (${or(this.invalid$data(), condition)})`) } error(append?: boolean, errorParams?: KeywordCxtParams, errorPaths?: ErrorPaths): void { if (errorParams) { this.setParams(errorParams) this._error(append, errorPaths) this.setParams({}) return } this._error(append, errorPaths) } private _error(append?: boolean, errorPaths?: ErrorPaths): void { ;(append ? reportExtraError : reportError)(this, this.def.error, errorPaths) } $dataError(): void { reportError(this, this.def.$dataError || keyword$DataError) } reset(): void { if (this.errsCount === undefined) throw new Error('add "trackErrors" to keyword definition') resetErrorsCount(this.gen, this.errsCount) } ok(cond: Code | boolean): void { if (!this.allErrors) this.gen.if(cond) } setParams(obj: KeywordCxtParams, assign?: true): void { if (assign) Object.assign(this.params, obj) else this.params = obj } block$data(valid: Name, codeBlock: () => void, $dataValid: Code = nil): void { this.gen.block(() => { this.check$data(valid, $dataValid) codeBlock() }) } check$data(valid: Name = nil, $dataValid: Code = nil): void { if (!this.$data) return const {gen, schemaCode, schemaType, def} = this gen.if(or(_`${schemaCode} === undefined`, $dataValid)) if (valid !== nil) gen.assign(valid, true) if (schemaType.length || def.validateSchema) { gen.elseIf(this.invalid$data()) this.$dataError() if (valid !== nil) gen.assign(valid, false) } gen.else() } invalid$data(): Code { const {gen, schemaCode, schemaType, def, it} = this return or(wrong$DataType(), invalid$DataSchema()) function wrong$DataType(): Code { if (schemaType.length) { /* istanbul ignore if */ if (!(schemaCode instanceof Name)) throw new Error("ajv implementation error") const st = Array.isArray(schemaType) ? schemaType : [schemaType] return _`${checkDataTypes(st, schemaCode, it.opts.strictNumbers, DataType.Wrong)}` } return nil } function invalid$DataSchema(): Code { if (def.validateSchema) { const validateSchemaRef = gen.scopeValue("validate$data", {ref: def.validateSchema}) // TODO value.code for standalone return _`!${validateSchemaRef}(${schemaCode})` } return nil } } subschema(appl: SubschemaArgs, valid: Name): SchemaCxt { const subschema = getSubschema(this.it, appl) extendSubschemaData(subschema, this.it, appl) extendSubschemaMode(subschema, appl) const nextContext = {...this.it, ...subschema, items: undefined, props: undefined} subschemaCode(nextContext, valid) return nextContext } mergeEvaluated(schemaCxt: SchemaCxt, toName?: typeof Name): void { const {it, gen} = this if (!it.opts.unevaluated) return if (it.props !== true && schemaCxt.props !== undefined) { it.props = mergeEvaluated.props(gen, schemaCxt.props, it.props, toName) } if (it.items !== true && schemaCxt.items !== undefined) { it.items = mergeEvaluated.items(gen, schemaCxt.items, it.items, toName) } } mergeValidEvaluated(schemaCxt: SchemaCxt, valid: Name): boolean | void { const {it, gen} = this if (it.opts.unevaluated && (it.props !== true || it.items !== true)) { gen.if(valid, () => this.mergeEvaluated(schemaCxt, Name)) return true } } } function keywordCode( it: SchemaObjCxt, keyword: string, def: AddedKeywordDefinition, ruleType?: JSONType ): void { const cxt = new KeywordCxt(it, def, keyword) if ("code" in def) { def.code(cxt, ruleType) } else if (cxt.$data && def.validate) { funcKeywordCode(cxt, def) } else if ("macro" in def) { macroKeywordCode(cxt, def) } else if (def.compile || def.validate) { funcKeywordCode(cxt, def) } } const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/ const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/ export function getData( $data: string, {dataLevel, dataNames, dataPathArr}: SchemaCxt ): Code | number { let jsonPointer let data: Code if ($data === "") return N.rootData if ($data[0] === "/") { if (!JSON_POINTER.test($data)) throw new Error(`Invalid JSON-pointer: ${$data}`) jsonPointer = $data data = N.rootData } else { const matches = RELATIVE_JSON_POINTER.exec($data) if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`) const up: number = +matches[1] jsonPointer = matches[2] if (jsonPointer === "#") { if (up >= dataLevel) throw new Error(errorMsg("property/index", up)) return dataPathArr[dataLevel - up] } if (up > dataLevel) throw new Error(errorMsg("data", up)) data = dataNames[dataLevel - up] if (!jsonPointer) return data } let expr = data const segments = jsonPointer.split("/") for (const segment of segments) { if (segment) { data = _`${data}${getProperty(unescapeJsonPointer(segment))}` expr = _`${expr} && ${data}` } } return expr function errorMsg(pointerType: string, up: number): string { return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}` } } ================================================ FILE: lib/compile/validate/keyword.ts ================================================ import type {KeywordCxt} from "." import type { AnySchema, SchemaValidateFunction, AnyValidateFunction, AddedKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, } from "../../types" import type {SchemaObjCxt} from ".." import {_, nil, not, stringify, Code, Name, CodeGen} from "../codegen" import N from "../names" import type {JSONType} from "../rules" import {callValidateCode} from "../../vocabularies/code" import {extendErrors} from "../errors" type KeywordCompilationResult = AnySchema | SchemaValidateFunction | AnyValidateFunction export function macroKeywordCode(cxt: KeywordCxt, def: MacroKeywordDefinition): void { const {gen, keyword, schema, parentSchema, it} = cxt const macroSchema = def.macro.call(it.self, schema, parentSchema, it) const schemaRef = useKeyword(gen, keyword, macroSchema) if (it.opts.validateSchema !== false) it.self.validateSchema(macroSchema, true) const valid = gen.name("valid") cxt.subschema( { schema: macroSchema, schemaPath: nil, errSchemaPath: `${it.errSchemaPath}/${keyword}`, topSchemaRef: schemaRef, compositeRule: true, }, valid ) cxt.pass(valid, () => cxt.error(true)) } export function funcKeywordCode(cxt: KeywordCxt, def: FuncKeywordDefinition): void { const {gen, keyword, schema, parentSchema, $data, it} = cxt checkAsyncKeyword(it, def) const validate = !$data && def.compile ? def.compile.call(it.self, schema, parentSchema, it) : def.validate const validateRef = useKeyword(gen, keyword, validate) const valid = gen.let("valid") cxt.block$data(valid, validateKeyword) cxt.ok(def.valid ?? valid) function validateKeyword(): void { if (def.errors === false) { assignValid() if (def.modifying) modifyData(cxt) reportErrs(() => cxt.error()) } else { const ruleErrs = def.async ? validateAsync() : validateSync() if (def.modifying) modifyData(cxt) reportErrs(() => addErrs(cxt, ruleErrs)) } } function validateAsync(): Name { const ruleErrs = gen.let("ruleErrs", null) gen.try( () => assignValid(_`await `), (e) => gen.assign(valid, false).if( _`${e} instanceof ${it.ValidationError as Name}`, () => gen.assign(ruleErrs, _`${e}.errors`), () => gen.throw(e) ) ) return ruleErrs } function validateSync(): Code { const validateErrs = _`${validateRef}.errors` gen.assign(validateErrs, null) assignValid(nil) return validateErrs } function assignValid(_await: Code = def.async ? _`await ` : nil): void { const passCxt = it.opts.passContext ? N.this : N.self const passSchema = !(("compile" in def && !$data) || def.schema === false) gen.assign( valid, _`${_await}${callValidateCode(cxt, validateRef, passCxt, passSchema)}`, def.modifying ) } function reportErrs(errors: () => void): void { gen.if(not(def.valid ?? valid), errors) } } function modifyData(cxt: KeywordCxt): void { const {gen, data, it} = cxt gen.if(it.parentData, () => gen.assign(data, _`${it.parentData}[${it.parentDataProperty}]`)) } function addErrs(cxt: KeywordCxt, errs: Code): void { const {gen} = cxt gen.if( _`Array.isArray(${errs})`, () => { gen .assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) .assign(N.errors, _`${N.vErrors}.length`) extendErrors(cxt) }, () => cxt.error() ) } function checkAsyncKeyword({schemaEnv}: SchemaObjCxt, def: FuncKeywordDefinition): void { if (def.async && !schemaEnv.$async) throw new Error("async keyword in sync schema") } function useKeyword(gen: CodeGen, keyword: string, result?: KeywordCompilationResult): Name { if (result === undefined) throw new Error(`keyword "${keyword}" failed to compile`) return gen.scopeValue( "keyword", typeof result == "function" ? {ref: result} : {ref: result, code: stringify(result)} ) } export function validSchemaType( schema: unknown, schemaType: JSONType[], allowUndefined = false ): boolean { // TODO add tests return ( !schemaType.length || schemaType.some((st) => st === "array" ? Array.isArray(schema) : st === "object" ? schema && typeof schema == "object" && !Array.isArray(schema) : typeof schema == st || (allowUndefined && typeof schema == "undefined") ) ) } export function validateKeywordUsage( {schema, opts, self, errSchemaPath}: SchemaObjCxt, def: AddedKeywordDefinition, keyword: string ): void { /* istanbul ignore if */ if (Array.isArray(def.keyword) ? !def.keyword.includes(keyword) : def.keyword !== keyword) { throw new Error("ajv implementation error") } const deps = def.dependencies if (deps?.some((kwd) => !Object.prototype.hasOwnProperty.call(schema, kwd))) { throw new Error(`parent schema must have dependencies of ${keyword}: ${deps.join(",")}`) } if (def.validateSchema) { const valid = def.validateSchema(schema[keyword]) if (!valid) { const msg = `keyword "${keyword}" value is invalid at path "${errSchemaPath}": ` + self.errorsText(def.validateSchema.errors) if (opts.validateSchema === "log") self.logger.error(msg) else throw new Error(msg) } } } ================================================ FILE: lib/compile/validate/subschema.ts ================================================ import type {AnySchema} from "../../types" import type {SchemaObjCxt} from ".." import {_, str, getProperty, Code, Name} from "../codegen" import {escapeFragment, getErrorPath, Type} from "../util" import type {JSONType} from "../rules" export interface SubschemaContext { // TODO use Optional? align with SchemCxt property types schema: AnySchema schemaPath: Code errSchemaPath: string topSchemaRef?: Code errorPath?: Code dataLevel?: number dataTypes?: JSONType[] data?: Name parentData?: Name parentDataProperty?: Code | number dataNames?: Name[] dataPathArr?: (Code | number)[] propertyName?: Name jtdDiscriminator?: string jtdMetadata?: boolean compositeRule?: true createErrors?: boolean allErrors?: boolean } export type SubschemaArgs = Partial<{ keyword: string schemaProp: string | number schema: AnySchema schemaPath: Code errSchemaPath: string topSchemaRef: Code data: Name | Code dataProp: Code | string | number dataTypes: JSONType[] definedProperties: Set propertyName: Name dataPropType: Type jtdDiscriminator: string jtdMetadata: boolean compositeRule: true createErrors: boolean allErrors: boolean }> export function getSubschema( it: SchemaObjCxt, {keyword, schemaProp, schema, schemaPath, errSchemaPath, topSchemaRef}: SubschemaArgs ): SubschemaContext { if (keyword !== undefined && schema !== undefined) { throw new Error('both "keyword" and "schema" passed, only one allowed') } if (keyword !== undefined) { const sch = it.schema[keyword] return schemaProp === undefined ? { schema: sch, schemaPath: _`${it.schemaPath}${getProperty(keyword)}`, errSchemaPath: `${it.errSchemaPath}/${keyword}`, } : { schema: sch[schemaProp], schemaPath: _`${it.schemaPath}${getProperty(keyword)}${getProperty(schemaProp)}`, errSchemaPath: `${it.errSchemaPath}/${keyword}/${escapeFragment(schemaProp)}`, } } if (schema !== undefined) { if (schemaPath === undefined || errSchemaPath === undefined || topSchemaRef === undefined) { throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"') } return { schema, schemaPath, topSchemaRef, errSchemaPath, } } throw new Error('either "keyword" or "schema" must be passed') } export function extendSubschemaData( subschema: SubschemaContext, it: SchemaObjCxt, {dataProp, dataPropType: dpType, data, dataTypes, propertyName}: SubschemaArgs ): void { if (data !== undefined && dataProp !== undefined) { throw new Error('both "data" and "dataProp" passed, only one allowed') } const {gen} = it if (dataProp !== undefined) { const {errorPath, dataPathArr, opts} = it const nextData = gen.let("data", _`${it.data}${getProperty(dataProp)}`, true) dataContextProps(nextData) subschema.errorPath = str`${errorPath}${getErrorPath(dataProp, dpType, opts.jsPropertySyntax)}` subschema.parentDataProperty = _`${dataProp}` subschema.dataPathArr = [...dataPathArr, subschema.parentDataProperty] } if (data !== undefined) { const nextData = data instanceof Name ? data : gen.let("data", data, true) // replaceable if used once? dataContextProps(nextData) if (propertyName !== undefined) subschema.propertyName = propertyName // TODO something is possibly wrong here with not changing parentDataProperty and not appending dataPathArr } if (dataTypes) subschema.dataTypes = dataTypes function dataContextProps(_nextData: Name): void { subschema.data = _nextData subschema.dataLevel = it.dataLevel + 1 subschema.dataTypes = [] it.definedProperties = new Set() subschema.parentData = it.data subschema.dataNames = [...it.dataNames, _nextData] } } export function extendSubschemaMode( subschema: SubschemaContext, {jtdDiscriminator, jtdMetadata, compositeRule, createErrors, allErrors}: SubschemaArgs ): void { if (compositeRule !== undefined) subschema.compositeRule = compositeRule if (createErrors !== undefined) subschema.createErrors = createErrors if (allErrors !== undefined) subschema.allErrors = allErrors subschema.jtdDiscriminator = jtdDiscriminator // not inherited subschema.jtdMetadata = jtdMetadata // not inherited } ================================================ FILE: lib/core.ts ================================================ export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, AnyValidateFunction, ErrorObject, ErrorNoParams, } from "./types" export {SchemaCxt, SchemaObjCxt} from "./compile" export interface Plugin { (ajv: Ajv, options?: Opts): Ajv [prop: string]: any } export {KeywordCxt} from "./compile/validate" export {DefinedError} from "./vocabularies/errors" export {JSONType} from "./compile/rules" export {JSONSchemaType} from "./types/json-schema" export {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema" export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen" import type { Schema, AnySchema, AnySchemaObject, SchemaObject, AsyncSchema, Vocabulary, KeywordDefinition, AddedKeywordDefinition, AnyValidateFunction, ValidateFunction, AsyncValidateFunction, ErrorObject, Format, AddedFormat, RegExpEngine, UriResolver, } from "./types" import type {JSONSchemaType} from "./types/json-schema" import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema" import ValidationError from "./runtime/validation_error" import MissingRefError from "./compile/ref_error" import {getRules, ValidationRules, Rule, RuleGroup, JSONType} from "./compile/rules" import {SchemaEnv, compileSchema, resolveSchema} from "./compile" import {Code, ValueScope} from "./compile/codegen" import {normalizeId, getSchemaRefs} from "./compile/resolve" import {getJSONTypes} from "./compile/validate/dataType" import {eachItem} from "./compile/util" import * as $dataRefSchema from "./refs/data.json" import DefaultUriResolver from "./runtime/uri" const defaultRegExp: RegExpEngine = (str, flags) => new RegExp(str, flags) defaultRegExp.code = "new RegExp" const META_IGNORE_OPTIONS: (keyof Options)[] = ["removeAdditional", "useDefaults", "coerceTypes"] const EXT_SCOPE_NAMES = new Set([ "validate", "serialize", "parse", "wrapper", "root", "schema", "keyword", "pattern", "formats", "validate$data", "func", "obj", "Error", ]) export type Options = CurrentOptions & DeprecatedOptions export interface CurrentOptions { // strict mode options (NEW) strict?: boolean | "log" strictSchema?: boolean | "log" strictNumbers?: boolean | "log" strictTypes?: boolean | "log" strictTuples?: boolean | "log" strictRequired?: boolean | "log" allowMatchingProperties?: boolean // disables a strict mode restriction allowUnionTypes?: boolean validateFormats?: boolean // validation and reporting options: $data?: boolean allErrors?: boolean verbose?: boolean discriminator?: boolean unicodeRegExp?: boolean timestamp?: "string" | "date" // JTD only parseDate?: boolean // JTD only allowDate?: boolean // JTD only specialNumbers?: "fast" | "null" // JTD only $comment?: | true | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown) formats?: {[Name in string]?: Format} keywords?: Vocabulary schemas?: AnySchema[] | {[Key in string]?: AnySchema} logger?: Logger | false loadSchema?: (uri: string) => Promise // options to modify validated data: removeAdditional?: boolean | "all" | "failing" useDefaults?: boolean | "empty" coerceTypes?: boolean | "array" // advanced options: next?: boolean // NEW unevaluated?: boolean // NEW dynamicRef?: boolean // NEW schemaId?: "id" | "$id" jtd?: boolean // NEW meta?: SchemaObject | boolean defaultMeta?: string | AnySchemaObject validateSchema?: boolean | "log" addUsedSchema?: boolean inlineRefs?: boolean | number passContext?: boolean loopRequired?: number loopEnum?: number // NEW ownProperties?: boolean multipleOfPrecision?: number int32range?: boolean // JTD only messages?: boolean code?: CodeOptions // NEW uriResolver?: UriResolver } export interface CodeOptions { es5?: boolean esm?: boolean lines?: boolean optimize?: boolean | number formats?: Code // code to require (or construct) map of available formats - for standalone code source?: boolean process?: (code: string, schema?: SchemaEnv) => string regExp?: RegExpEngine } interface InstanceCodeOptions extends CodeOptions { regExp: RegExpEngine optimize: number } interface DeprecatedOptions { /** @deprecated */ ignoreKeywordsWithRef?: boolean /** @deprecated */ jsPropertySyntax?: boolean // added instead of jsonPointers /** @deprecated */ unicode?: boolean } interface RemovedOptions { format?: boolean errorDataPath?: "object" | "property" nullable?: boolean // "nullable" keyword is supported by default jsonPointers?: boolean extendRefs?: true | "ignore" | "fail" missingRefs?: true | "ignore" | "fail" processCode?: (code: string, schema?: SchemaEnv) => string sourceCode?: boolean strictDefaults?: boolean strictKeywords?: boolean uniqueItems?: boolean unknownFormats?: true | string[] | "ignore" cache?: any serialize?: (schema: AnySchema) => unknown ajvErrors?: boolean } type OptionsInfo = { [K in keyof T]-?: string | undefined } const removedOptions: OptionsInfo = { errorDataPath: "", format: "`validateFormats: false` can be used instead.", nullable: '"nullable" keyword is supported by default.', jsonPointers: "Deprecated jsPropertySyntax can be used instead.", extendRefs: "Deprecated ignoreKeywordsWithRef can be used instead.", missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.", processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`", sourceCode: "Use option `code: {source: true}`", strictDefaults: "It is default now, see option `strict`.", strictKeywords: "It is default now, see option `strict`.", uniqueItems: '"uniqueItems" keyword is always validated.', unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).", cache: "Map is used as cache, schema object as key.", serialize: "Map is used as cache, schema object as key.", ajvErrors: "It is default now.", } const deprecatedOptions: OptionsInfo = { ignoreKeywordsWithRef: "", jsPropertySyntax: "", unicode: '"minLength"/"maxLength" account for unicode characters by default.', } type RequiredInstanceOptions = { [K in | "strictSchema" | "strictNumbers" | "strictTypes" | "strictTuples" | "strictRequired" | "inlineRefs" | "loopRequired" | "loopEnum" | "meta" | "messages" | "schemaId" | "addUsedSchema" | "validateSchema" | "validateFormats" | "int32range" | "unicodeRegExp" | "uriResolver"]: NonNullable } & {code: InstanceCodeOptions} export type InstanceOptions = Options & RequiredInstanceOptions const MAX_EXPRESSION = 200 // eslint-disable-next-line complexity function requiredOptions(o: Options): RequiredInstanceOptions { const s = o.strict const _optz = o.code?.optimize const optimize = _optz === true || _optz === undefined ? 1 : _optz || 0 const regExp = o.code?.regExp ?? defaultRegExp const uriResolver = o.uriResolver ?? DefaultUriResolver return { strictSchema: o.strictSchema ?? s ?? true, strictNumbers: o.strictNumbers ?? s ?? true, strictTypes: o.strictTypes ?? s ?? "log", strictTuples: o.strictTuples ?? s ?? "log", strictRequired: o.strictRequired ?? s ?? false, code: o.code ? {...o.code, optimize, regExp} : {optimize, regExp}, loopRequired: o.loopRequired ?? MAX_EXPRESSION, loopEnum: o.loopEnum ?? MAX_EXPRESSION, meta: o.meta ?? true, messages: o.messages ?? true, inlineRefs: o.inlineRefs ?? true, schemaId: o.schemaId ?? "$id", addUsedSchema: o.addUsedSchema ?? true, validateSchema: o.validateSchema ?? true, validateFormats: o.validateFormats ?? true, unicodeRegExp: o.unicodeRegExp ?? true, int32range: o.int32range ?? true, uriResolver: uriResolver, } } export interface Logger { log(...args: unknown[]): unknown warn(...args: unknown[]): unknown error(...args: unknown[]): unknown } export default class Ajv { opts: InstanceOptions errors?: ErrorObject[] | null // errors from the last validation logger: Logger // shared external scope values for compiled functions readonly scope: ValueScope readonly schemas: {[Key in string]?: SchemaEnv} = {} readonly refs: {[Ref in string]?: SchemaEnv | string} = {} readonly formats: {[Name in string]?: AddedFormat} = {} readonly RULES: ValidationRules readonly _compilations: Set = new Set() private readonly _loading: {[Ref in string]?: Promise} = {} private readonly _cache: Map = new Map() private readonly _metaOpts: InstanceOptions static ValidationError = ValidationError static MissingRefError = MissingRefError constructor(opts: Options = {}) { opts = this.opts = {...opts, ...requiredOptions(opts)} const {es5, lines} = this.opts.code this.scope = new ValueScope({scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines}) this.logger = getLogger(opts.logger) const formatOpt = opts.validateFormats opts.validateFormats = false this.RULES = getRules() checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED") checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn") this._metaOpts = getMetaSchemaOptions.call(this) if (opts.formats) addInitialFormats.call(this) this._addVocabularies() this._addDefaultMetaSchema() if (opts.keywords) addInitialKeywords.call(this, opts.keywords) if (typeof opts.meta == "object") this.addMetaSchema(opts.meta) addInitialSchemas.call(this) opts.validateFormats = formatOpt } _addVocabularies(): void { this.addKeyword("$async") } _addDefaultMetaSchema(): void { const {$data, meta, schemaId} = this.opts let _dataRefSchema: SchemaObject = $dataRefSchema if (schemaId === "id") { _dataRefSchema = {...$dataRefSchema} _dataRefSchema.id = _dataRefSchema.$id delete _dataRefSchema.$id } if (meta && $data) this.addMetaSchema(_dataRefSchema, _dataRefSchema[schemaId], false) } defaultMeta(): string | AnySchemaObject | undefined { const {meta, schemaId} = this.opts return (this.opts.defaultMeta = typeof meta == "object" ? meta[schemaId] || meta : undefined) } // Validate data using schema // AnySchema will be compiled and cached using schema itself as a key for Map validate(schema: Schema | string, data: unknown): boolean validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise validate(schema: Schema | JSONSchemaType | string, data: unknown): data is T // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures validate(schema: JTDSchemaType, data: unknown): data is T // This overload is only intended for typescript inference, the first // argument prevents manual type annotation from matching this overload // eslint-disable-next-line @typescript-eslint/no-unused-vars validate( schema: T, data: unknown ): data is JTDDataType // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents validate(schema: AsyncSchema, data: unknown | T): Promise validate(schemaKeyRef: AnySchema | string, data: unknown): data is T | Promise validate( schemaKeyRef: AnySchema | string, // key, ref or schema object // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents data: unknown | T // to be validated ): boolean | Promise { let v: AnyValidateFunction | undefined if (typeof schemaKeyRef == "string") { v = this.getSchema(schemaKeyRef) if (!v) throw new Error(`no schema with key or ref "${schemaKeyRef}"`) } else { v = this.compile(schemaKeyRef) } const valid = v(data) if (!("$async" in v)) this.errors = v.errors return valid } // Create validation function for passed schema // _meta: true if schema is a meta-schema. Used internally to compile meta schemas of user-defined keywords. compile(schema: Schema | JSONSchemaType, _meta?: boolean): ValidateFunction // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures compile(schema: JTDSchemaType, _meta?: boolean): ValidateFunction // This overload is only intended for typescript inference, the first // argument prevents manual type annotation from matching this overload // eslint-disable-next-line @typescript-eslint/no-unused-vars compile( schema: T, _meta?: boolean ): ValidateFunction> compile(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction compile(schema: AnySchema, _meta?: boolean): AnyValidateFunction compile(schema: AnySchema, _meta?: boolean): AnyValidateFunction { const sch = this._addSchema(schema, _meta) return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction } // Creates validating function for passed schema with asynchronous loading of missing schemas. // `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema. // TODO allow passing schema URI // meta - optional true to compile meta-schema compileAsync( schema: SchemaObject | JSONSchemaType, _meta?: boolean ): Promise> // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures compileAsync(schema: JTDSchemaType, _meta?: boolean): Promise> compileAsync(schema: AsyncSchema, meta?: boolean): Promise> // eslint-disable-next-line @typescript-eslint/unified-signatures compileAsync( schema: AnySchemaObject, meta?: boolean ): Promise> compileAsync( schema: AnySchemaObject, meta?: boolean ): Promise> { if (typeof this.opts.loadSchema != "function") { throw new Error("options.loadSchema should be a function") } const {loadSchema} = this.opts return runCompileAsync.call(this, schema, meta) async function runCompileAsync( this: Ajv, _schema: AnySchemaObject, _meta?: boolean ): Promise { await loadMetaSchema.call(this, _schema.$schema) const sch = this._addSchema(_schema, _meta) return sch.validate || _compileAsync.call(this, sch) } async function loadMetaSchema(this: Ajv, $ref?: string): Promise { if ($ref && !this.getSchema($ref)) { await runCompileAsync.call(this, {$ref}, true) } } async function _compileAsync(this: Ajv, sch: SchemaEnv): Promise { try { return this._compileSchemaEnv(sch) } catch (e) { if (!(e instanceof MissingRefError)) throw e checkLoaded.call(this, e) await loadMissingSchema.call(this, e.missingSchema) return _compileAsync.call(this, sch) } } function checkLoaded(this: Ajv, {missingSchema: ref, missingRef}: MissingRefError): void { if (this.refs[ref]) { throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`) } } async function loadMissingSchema(this: Ajv, ref: string): Promise { const _schema = await _loadSchema.call(this, ref) if (!this.refs[ref]) await loadMetaSchema.call(this, _schema.$schema) if (!this.refs[ref]) this.addSchema(_schema, ref, meta) } async function _loadSchema(this: Ajv, ref: string): Promise { const p = this._loading[ref] if (p) return p try { return await (this._loading[ref] = loadSchema(ref)) } finally { delete this._loading[ref] } } } // Adds schema to the instance addSchema( schema: AnySchema | AnySchema[], // If array is passed, `key` will be ignored key?: string, // Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`. _meta?: boolean, // true if schema is a meta-schema. Used internally, addMetaSchema should be used instead. _validateSchema = this.opts.validateSchema // false to skip schema validation. Used internally, option validateSchema should be used instead. ): Ajv { if (Array.isArray(schema)) { for (const sch of schema) this.addSchema(sch, undefined, _meta, _validateSchema) return this } let id: string | undefined if (typeof schema === "object") { const {schemaId} = this.opts id = schema[schemaId] if (id !== undefined && typeof id != "string") { throw new Error(`schema ${schemaId} must be string`) } } key = normalizeId(key || id) this._checkUnique(key) this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true) return this } // Add schema that will be used to validate other schemas // options in META_IGNORE_OPTIONS are alway set to false addMetaSchema( schema: AnySchemaObject, key?: string, // schema key _validateSchema = this.opts.validateSchema // false to skip schema validation, can be used to override validateSchema option for meta-schema ): Ajv { this.addSchema(schema, key, true, _validateSchema) return this } // Validate schema against its meta-schema validateSchema(schema: AnySchema, throwOrLogError?: boolean): boolean | Promise { if (typeof schema == "boolean") return true let $schema: string | AnySchemaObject | undefined $schema = schema.$schema if ($schema !== undefined && typeof $schema != "string") { throw new Error("$schema must be a string") } $schema = $schema || this.opts.defaultMeta || this.defaultMeta() if (!$schema) { this.logger.warn("meta-schema not available") this.errors = null return true } const valid = this.validate($schema, schema) if (!valid && throwOrLogError) { const message = "schema is invalid: " + this.errorsText() if (this.opts.validateSchema === "log") this.logger.error(message) else throw new Error(message) } return valid } // Get compiled schema by `key` or `ref`. // (`key` that was passed to `addSchema` or full schema reference - `schema.$id` or resolved id) getSchema(keyRef: string): AnyValidateFunction | undefined { let sch while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch if (sch === undefined) { const {schemaId} = this.opts const root = new SchemaEnv({schema: {}, schemaId}) sch = resolveSchema.call(this, root, keyRef) if (!sch) return this.refs[keyRef] = sch } return (sch.validate || this._compileSchemaEnv(sch)) as AnyValidateFunction | undefined } // Remove cached schema(s). // If no parameter is passed all schemas but meta-schemas are removed. // If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed. // Even if schema is referenced by other schemas it still can be removed as other schemas have local references. removeSchema(schemaKeyRef?: AnySchema | string | RegExp): Ajv { if (schemaKeyRef instanceof RegExp) { this._removeAllSchemas(this.schemas, schemaKeyRef) this._removeAllSchemas(this.refs, schemaKeyRef) return this } switch (typeof schemaKeyRef) { case "undefined": this._removeAllSchemas(this.schemas) this._removeAllSchemas(this.refs) this._cache.clear() return this case "string": { const sch = getSchEnv.call(this, schemaKeyRef) if (typeof sch == "object") this._cache.delete(sch.schema) delete this.schemas[schemaKeyRef] delete this.refs[schemaKeyRef] return this } case "object": { const cacheKey = schemaKeyRef this._cache.delete(cacheKey) let id = schemaKeyRef[this.opts.schemaId] if (id) { id = normalizeId(id) delete this.schemas[id] delete this.refs[id] } return this } default: throw new Error("ajv.removeSchema: invalid parameter") } } // add "vocabulary" - a collection of keywords addVocabulary(definitions: Vocabulary): Ajv { for (const def of definitions) this.addKeyword(def) return this } addKeyword( kwdOrDef: string | KeywordDefinition, def?: KeywordDefinition // deprecated ): Ajv { let keyword: string | string[] if (typeof kwdOrDef == "string") { keyword = kwdOrDef if (typeof def == "object") { this.logger.warn("these parameters are deprecated, see docs for addKeyword") def.keyword = keyword } } else if (typeof kwdOrDef == "object" && def === undefined) { def = kwdOrDef keyword = def.keyword if (Array.isArray(keyword) && !keyword.length) { throw new Error("addKeywords: keyword must be string or non-empty array") } } else { throw new Error("invalid addKeywords parameters") } checkKeyword.call(this, keyword, def) if (!def) { eachItem(keyword, (kwd) => addRule.call(this, kwd)) return this } keywordMetaschema.call(this, def) const definition: AddedKeywordDefinition = { ...def, type: getJSONTypes(def.type), schemaType: getJSONTypes(def.schemaType), } eachItem( keyword, definition.type.length === 0 ? (k) => addRule.call(this, k, definition) : (k) => definition.type.forEach((t) => addRule.call(this, k, definition, t)) ) return this } getKeyword(keyword: string): AddedKeywordDefinition | boolean { const rule = this.RULES.all[keyword] return typeof rule == "object" ? rule.definition : !!rule } // Remove keyword removeKeyword(keyword: string): Ajv { // TODO return type should be Ajv const {RULES} = this delete RULES.keywords[keyword] delete RULES.all[keyword] for (const group of RULES.rules) { const i = group.rules.findIndex((rule) => rule.keyword === keyword) if (i >= 0) group.rules.splice(i, 1) } return this } // Add format addFormat(name: string, format: Format): Ajv { if (typeof format == "string") format = new RegExp(format) this.formats[name] = format return this } errorsText( errors: ErrorObject[] | null | undefined = this.errors, // optional array of validation errors {separator = ", ", dataVar = "data"}: ErrorsTextOptions = {} // optional options with properties `separator` and `dataVar` ): string { if (!errors || errors.length === 0) return "No errors" return errors .map((e) => `${dataVar}${e.instancePath} ${e.message}`) .reduce((text, msg) => text + separator + msg) } $dataMetaSchema(metaSchema: AnySchemaObject, keywordsJsonPointers: string[]): AnySchemaObject { const rules = this.RULES.all metaSchema = JSON.parse(JSON.stringify(metaSchema)) for (const jsonPointer of keywordsJsonPointers) { const segments = jsonPointer.split("/").slice(1) // first segment is an empty string let keywords = metaSchema for (const seg of segments) keywords = keywords[seg] as AnySchemaObject for (const key in rules) { const rule = rules[key] if (typeof rule != "object") continue const {$data} = rule.definition const schema = keywords[key] as AnySchemaObject | undefined if ($data && schema) keywords[key] = schemaOrData(schema) } } return metaSchema } private _removeAllSchemas(schemas: {[Ref in string]?: SchemaEnv | string}, regex?: RegExp): void { for (const keyRef in schemas) { const sch = schemas[keyRef] if (!regex || regex.test(keyRef)) { if (typeof sch == "string") { delete schemas[keyRef] } else if (sch && !sch.meta) { this._cache.delete(sch.schema) delete schemas[keyRef] } } } } _addSchema( schema: AnySchema, meta?: boolean, baseId?: string, validateSchema = this.opts.validateSchema, addSchema = this.opts.addUsedSchema ): SchemaEnv { let id: string | undefined const {schemaId} = this.opts if (typeof schema == "object") { id = schema[schemaId] } else { if (this.opts.jtd) throw new Error("schema must be object") else if (typeof schema != "boolean") throw new Error("schema must be object or boolean") } let sch = this._cache.get(schema) if (sch !== undefined) return sch baseId = normalizeId(id || baseId) const localRefs = getSchemaRefs.call(this, schema, baseId) sch = new SchemaEnv({schema, schemaId, meta, baseId, localRefs}) this._cache.set(sch.schema, sch) if (addSchema && !baseId.startsWith("#")) { // TODO atm it is allowed to overwrite schemas without id (instead of not adding them) if (baseId) this._checkUnique(baseId) this.refs[baseId] = sch } if (validateSchema) this.validateSchema(schema, true) return sch } private _checkUnique(id: string): void { if (this.schemas[id] || this.refs[id]) { throw new Error(`schema with key or id "${id}" already exists`) } } private _compileSchemaEnv(sch: SchemaEnv): AnyValidateFunction { if (sch.meta) this._compileMetaSchema(sch) else compileSchema.call(this, sch) /* istanbul ignore if */ if (!sch.validate) throw new Error("ajv implementation error") return sch.validate } private _compileMetaSchema(sch: SchemaEnv): void { const currentOpts = this.opts this.opts = this._metaOpts try { compileSchema.call(this, sch) } finally { this.opts = currentOpts } } } export interface ErrorsTextOptions { separator?: string dataVar?: string } function checkOptions( this: Ajv, checkOpts: OptionsInfo, options: Options & RemovedOptions, msg: string, log: "warn" | "error" = "error" ): void { for (const key in checkOpts) { const opt = key as keyof typeof checkOpts if (opt in options) this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`) } } function getSchEnv(this: Ajv, keyRef: string): SchemaEnv | string | undefined { keyRef = normalizeId(keyRef) // TODO tests fail without this line return this.schemas[keyRef] || this.refs[keyRef] } function addInitialSchemas(this: Ajv): void { const optsSchemas = this.opts.schemas if (!optsSchemas) return if (Array.isArray(optsSchemas)) this.addSchema(optsSchemas) else for (const key in optsSchemas) this.addSchema(optsSchemas[key] as AnySchema, key) } function addInitialFormats(this: Ajv): void { for (const name in this.opts.formats) { const format = this.opts.formats[name] if (format) this.addFormat(name, format) } } function addInitialKeywords( this: Ajv, defs: Vocabulary | {[K in string]?: KeywordDefinition} ): void { if (Array.isArray(defs)) { this.addVocabulary(defs) return } this.logger.warn("keywords option as map is deprecated, pass array") for (const keyword in defs) { const def = defs[keyword] as KeywordDefinition if (!def.keyword) def.keyword = keyword this.addKeyword(def) } } function getMetaSchemaOptions(this: Ajv): InstanceOptions { const metaOpts = {...this.opts} for (const opt of META_IGNORE_OPTIONS) delete metaOpts[opt] return metaOpts } const noLogs = {log() {}, warn() {}, error() {}} function getLogger(logger?: Partial | false): Logger { if (logger === false) return noLogs if (logger === undefined) return console if (logger.log && logger.warn && logger.error) return logger as Logger throw new Error("logger must implement log, warn and error methods") } const KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i function checkKeyword(this: Ajv, keyword: string | string[], def?: KeywordDefinition): void { const {RULES} = this eachItem(keyword, (kwd) => { if (RULES.keywords[kwd]) throw new Error(`Keyword ${kwd} is already defined`) if (!KEYWORD_NAME.test(kwd)) throw new Error(`Keyword ${kwd} has invalid name`) }) if (!def) return if (def.$data && !("code" in def || "validate" in def)) { throw new Error('$data keyword must have "code" or "validate" function') } } function addRule( this: Ajv, keyword: string, definition?: AddedKeywordDefinition, dataType?: JSONType ): void { const post = definition?.post if (dataType && post) throw new Error('keyword with "post" flag cannot have "type"') const {RULES} = this let ruleGroup = post ? RULES.post : RULES.rules.find(({type: t}) => t === dataType) if (!ruleGroup) { ruleGroup = {type: dataType, rules: []} RULES.rules.push(ruleGroup) } RULES.keywords[keyword] = true if (!definition) return const rule: Rule = { keyword, definition: { ...definition, type: getJSONTypes(definition.type), schemaType: getJSONTypes(definition.schemaType), }, } if (definition.before) addBeforeRule.call(this, ruleGroup, rule, definition.before) else ruleGroup.rules.push(rule) RULES.all[keyword] = rule definition.implements?.forEach((kwd) => this.addKeyword(kwd)) } function addBeforeRule(this: Ajv, ruleGroup: RuleGroup, rule: Rule, before: string): void { const i = ruleGroup.rules.findIndex((_rule) => _rule.keyword === before) if (i >= 0) { ruleGroup.rules.splice(i, 0, rule) } else { ruleGroup.rules.push(rule) this.logger.warn(`rule ${before} is not defined`) } } function keywordMetaschema(this: Ajv, def: KeywordDefinition): void { let {metaSchema} = def if (metaSchema === undefined) return if (def.$data && this.opts.$data) metaSchema = schemaOrData(metaSchema) def.validateSchema = this.compile(metaSchema, true) } const $dataRef = { $ref: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#", } function schemaOrData(schema: AnySchema): AnySchemaObject { return {anyOf: [schema, $dataRef]} } ================================================ FILE: lib/jtd.ts ================================================ import type {AnySchemaObject, SchemaObject, JTDParser} from "./types" import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "./types/jtd-schema" import AjvCore, {CurrentOptions} from "./core" import jtdVocabulary from "./vocabularies/jtd" import jtdMetaSchema from "./refs/jtd-schema" import compileSerializer from "./compile/jtd/serialize" import compileParser from "./compile/jtd/parse" import {SchemaEnv} from "./compile" const META_SCHEMA_ID = "JTD-meta-schema" type JTDOptions = CurrentOptions & { // strict mode options not supported with JTD: strict?: never allowMatchingProperties?: never allowUnionTypes?: never validateFormats?: never // validation and reporting options not supported with JTD: $data?: never verbose?: boolean $comment?: never formats?: never loadSchema?: never // options to modify validated data: useDefaults?: never coerceTypes?: never // advanced options: next?: never unevaluated?: never dynamicRef?: never meta?: boolean defaultMeta?: never inlineRefs?: boolean loopRequired?: never multipleOfPrecision?: never } export class Ajv extends AjvCore { constructor(opts: JTDOptions = {}) { super({ ...opts, jtd: true, }) } _addVocabularies(): void { super._addVocabularies() this.addVocabulary(jtdVocabulary) } _addDefaultMetaSchema(): void { super._addDefaultMetaSchema() if (!this.opts.meta) return this.addMetaSchema(jtdMetaSchema, META_SCHEMA_ID, false) } defaultMeta(): string | AnySchemaObject | undefined { return (this.opts.defaultMeta = super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : undefined)) } compileSerializer(schema: SchemaObject): (data: T) => string // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures compileSerializer(schema: JTDSchemaType): (data: T) => string compileSerializer(schema: SchemaObject): (data: T) => string { const sch = this._addSchema(schema) return sch.serialize || this._compileSerializer(sch) } compileParser(schema: SchemaObject): JTDParser // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures compileParser(schema: JTDSchemaType): JTDParser compileParser(schema: SchemaObject): JTDParser { const sch = this._addSchema(schema) return (sch.parse || this._compileParser(sch)) as JTDParser } private _compileSerializer(sch: SchemaEnv): (data: T) => string { compileSerializer.call(this, sch, (sch.schema as AnySchemaObject).definitions || {}) /* istanbul ignore if */ if (!sch.serialize) throw new Error("ajv implementation error") return sch.serialize } private _compileParser(sch: SchemaEnv): JTDParser { compileParser.call(this, sch, (sch.schema as AnySchemaObject).definitions || {}) /* istanbul ignore if */ if (!sch.parse) throw new Error("ajv implementation error") return sch.parse } } module.exports = exports = Ajv module.exports.Ajv = Ajv Object.defineProperty(exports, "__esModule", {value: true}) export default Ajv export { Format, FormatDefinition, AsyncFormatDefinition, KeywordDefinition, KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, Vocabulary, Schema, SchemaObject, AnySchemaObject, AsyncSchema, AnySchema, ValidateFunction, AsyncValidateFunction, ErrorObject, ErrorNoParams, JTDParser, } from "./types" export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core" export {SchemaCxt, SchemaObjCxt} from "./compile" export {KeywordCxt} from "./compile/validate" export {JTDErrorObject} from "./vocabularies/jtd" export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen" export {JTDSchemaType, SomeJTDSchemaType, JTDDataType} export {JTDOptions} export {default as ValidationError} from "./runtime/validation_error" export {default as MissingRefError} from "./compile/ref_error" ================================================ FILE: lib/refs/data.json ================================================ { "$id": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#", "description": "Meta-schema for $data reference (JSON AnySchema extension proposal)", "type": "object", "required": ["$data"], "properties": { "$data": { "type": "string", "anyOf": [{"format": "relative-json-pointer"}, {"format": "json-pointer"}] } }, "additionalProperties": false } ================================================ FILE: lib/refs/json-schema-2019-09/index.ts ================================================ import type Ajv from "../../core" import type {AnySchemaObject} from "../../types" import * as metaSchema from "./schema.json" import * as applicator from "./meta/applicator.json" import * as content from "./meta/content.json" import * as core from "./meta/core.json" import * as format from "./meta/format.json" import * as metadata from "./meta/meta-data.json" import * as validation from "./meta/validation.json" const META_SUPPORT_DATA = ["/properties"] export default function addMetaSchema2019(this: Ajv, $data?: boolean): Ajv { ;[ metaSchema, applicator, content, core, with$data(this, format), metadata, with$data(this, validation), ].forEach((sch) => this.addMetaSchema(sch, undefined, false)) return this function with$data(ajv: Ajv, sch: AnySchemaObject): AnySchemaObject { return $data ? ajv.$dataMetaSchema(sch, META_SUPPORT_DATA) : sch } } ================================================ FILE: lib/refs/json-schema-2019-09/meta/applicator.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/applicator", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/applicator": true }, "$recursiveAnchor": true, "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "additionalItems": {"$recursiveRef": "#"}, "unevaluatedItems": {"$recursiveRef": "#"}, "items": { "anyOf": [{"$recursiveRef": "#"}, {"$ref": "#/$defs/schemaArray"}] }, "contains": {"$recursiveRef": "#"}, "additionalProperties": {"$recursiveRef": "#"}, "unevaluatedProperties": {"$recursiveRef": "#"}, "properties": { "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "propertyNames": {"format": "regex"}, "default": {} }, "dependentSchemas": { "type": "object", "additionalProperties": { "$recursiveRef": "#" } }, "propertyNames": {"$recursiveRef": "#"}, "if": {"$recursiveRef": "#"}, "then": {"$recursiveRef": "#"}, "else": {"$recursiveRef": "#"}, "allOf": {"$ref": "#/$defs/schemaArray"}, "anyOf": {"$ref": "#/$defs/schemaArray"}, "oneOf": {"$ref": "#/$defs/schemaArray"}, "not": {"$recursiveRef": "#"} }, "$defs": { "schemaArray": { "type": "array", "minItems": 1, "items": {"$recursiveRef": "#"} } } } ================================================ FILE: lib/refs/json-schema-2019-09/meta/content.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/content", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/content": true }, "$recursiveAnchor": true, "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "contentMediaType": {"type": "string"}, "contentEncoding": {"type": "string"}, "contentSchema": {"$recursiveRef": "#"} } } ================================================ FILE: lib/refs/json-schema-2019-09/meta/core.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/core", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true }, "$recursiveAnchor": true, "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$" }, "$schema": { "type": "string", "format": "uri" }, "$anchor": { "type": "string", "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" }, "$ref": { "type": "string", "format": "uri-reference" }, "$recursiveRef": { "type": "string", "format": "uri-reference" }, "$recursiveAnchor": { "type": "boolean", "default": false }, "$vocabulary": { "type": "object", "propertyNames": { "type": "string", "format": "uri" }, "additionalProperties": { "type": "boolean" } }, "$comment": { "type": "string" }, "$defs": { "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {} } } } ================================================ FILE: lib/refs/json-schema-2019-09/meta/format.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/format", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/format": true }, "$recursiveAnchor": true, "title": "Format vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "format": {"type": "string"} } } ================================================ FILE: lib/refs/json-schema-2019-09/meta/meta-data.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/meta-data": true }, "$recursiveAnchor": true, "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "deprecated": { "type": "boolean", "default": false }, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true } } } ================================================ FILE: lib/refs/json-schema-2019-09/meta/validation.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/validation", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/validation": true }, "$recursiveAnchor": true, "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": {"$ref": "#/$defs/nonNegativeInteger"}, "minLength": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "pattern": { "type": "string", "format": "regex" }, "maxItems": {"$ref": "#/$defs/nonNegativeInteger"}, "minItems": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "uniqueItems": { "type": "boolean", "default": false }, "maxContains": {"$ref": "#/$defs/nonNegativeInteger"}, "minContains": { "$ref": "#/$defs/nonNegativeInteger", "default": 1 }, "maxProperties": {"$ref": "#/$defs/nonNegativeInteger"}, "minProperties": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/$defs/stringArray"}, "dependentRequired": { "type": "object", "additionalProperties": { "$ref": "#/$defs/stringArray" } }, "const": true, "enum": { "type": "array", "items": true }, "type": { "anyOf": [ {"$ref": "#/$defs/simpleTypes"}, { "type": "array", "items": {"$ref": "#/$defs/simpleTypes"}, "minItems": 1, "uniqueItems": true } ] } }, "$defs": { "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 }, "simpleTypes": { "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": [] } } } ================================================ FILE: lib/refs/json-schema-2019-09/schema.json ================================================ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/schema", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true, "https://json-schema.org/draft/2019-09/vocab/applicator": true, "https://json-schema.org/draft/2019-09/vocab/validation": true, "https://json-schema.org/draft/2019-09/vocab/meta-data": true, "https://json-schema.org/draft/2019-09/vocab/format": false, "https://json-schema.org/draft/2019-09/vocab/content": true }, "$recursiveAnchor": true, "title": "Core and Validation specifications meta-schema", "allOf": [ {"$ref": "meta/core"}, {"$ref": "meta/applicator"}, {"$ref": "meta/validation"}, {"$ref": "meta/meta-data"}, {"$ref": "meta/format"}, {"$ref": "meta/content"} ], "type": ["object", "boolean"], "properties": { "definitions": { "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {} }, "dependencies": { "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", "type": "object", "additionalProperties": { "anyOf": [{"$recursiveRef": "#"}, {"$ref": "meta/validation#/$defs/stringArray"}] } } } } ================================================ FILE: lib/refs/json-schema-2020-12/index.ts ================================================ import type Ajv from "../../core" import type {AnySchemaObject} from "../../types" import * as metaSchema from "./schema.json" import * as applicator from "./meta/applicator.json" import * as unevaluated from "./meta/unevaluated.json" import * as content from "./meta/content.json" import * as core from "./meta/core.json" import * as format from "./meta/format-annotation.json" import * as metadata from "./meta/meta-data.json" import * as validation from "./meta/validation.json" const META_SUPPORT_DATA = ["/properties"] export default function addMetaSchema2020(this: Ajv, $data?: boolean): Ajv { ;[ metaSchema, applicator, unevaluated, content, core, with$data(this, format), metadata, with$data(this, validation), ].forEach((sch) => this.addMetaSchema(sch, undefined, false)) return this function with$data(ajv: Ajv, sch: AnySchemaObject): AnySchemaObject { return $data ? ajv.$dataMetaSchema(sch, META_SUPPORT_DATA) : sch } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/applicator.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/applicator", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/applicator": true }, "$dynamicAnchor": "meta", "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "prefixItems": {"$ref": "#/$defs/schemaArray"}, "items": {"$dynamicRef": "#meta"}, "contains": {"$dynamicRef": "#meta"}, "additionalProperties": {"$dynamicRef": "#meta"}, "properties": { "type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "propertyNames": {"format": "regex"}, "default": {} }, "dependentSchemas": { "type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "default": {} }, "propertyNames": {"$dynamicRef": "#meta"}, "if": {"$dynamicRef": "#meta"}, "then": {"$dynamicRef": "#meta"}, "else": {"$dynamicRef": "#meta"}, "allOf": {"$ref": "#/$defs/schemaArray"}, "anyOf": {"$ref": "#/$defs/schemaArray"}, "oneOf": {"$ref": "#/$defs/schemaArray"}, "not": {"$dynamicRef": "#meta"} }, "$defs": { "schemaArray": { "type": "array", "minItems": 1, "items": {"$dynamicRef": "#meta"} } } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/content.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/content", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/content": true }, "$dynamicAnchor": "meta", "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "contentEncoding": {"type": "string"}, "contentMediaType": {"type": "string"}, "contentSchema": {"$dynamicRef": "#meta"} } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/core.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/core", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true }, "$dynamicAnchor": "meta", "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "$id": { "$ref": "#/$defs/uriReferenceString", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$" }, "$schema": {"$ref": "#/$defs/uriString"}, "$ref": {"$ref": "#/$defs/uriReferenceString"}, "$anchor": {"$ref": "#/$defs/anchorString"}, "$dynamicRef": {"$ref": "#/$defs/uriReferenceString"}, "$dynamicAnchor": {"$ref": "#/$defs/anchorString"}, "$vocabulary": { "type": "object", "propertyNames": {"$ref": "#/$defs/uriString"}, "additionalProperties": { "type": "boolean" } }, "$comment": { "type": "string" }, "$defs": { "type": "object", "additionalProperties": {"$dynamicRef": "#meta"} } }, "$defs": { "anchorString": { "type": "string", "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" }, "uriString": { "type": "string", "format": "uri" }, "uriReferenceString": { "type": "string", "format": "uri-reference" } } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/format-annotation.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/format-annotation": true }, "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema for annotation results", "type": ["object", "boolean"], "properties": { "format": {"type": "string"} } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/meta-data.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/meta-data": true }, "$dynamicAnchor": "meta", "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "deprecated": { "type": "boolean", "default": false }, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true } } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/unevaluated.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/unevaluated": true }, "$dynamicAnchor": "meta", "title": "Unevaluated applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "unevaluatedItems": {"$dynamicRef": "#meta"}, "unevaluatedProperties": {"$dynamicRef": "#meta"} } } ================================================ FILE: lib/refs/json-schema-2020-12/meta/validation.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/validation", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/validation": true }, "$dynamicAnchor": "meta", "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "type": { "anyOf": [ {"$ref": "#/$defs/simpleTypes"}, { "type": "array", "items": {"$ref": "#/$defs/simpleTypes"}, "minItems": 1, "uniqueItems": true } ] }, "const": true, "enum": { "type": "array", "items": true }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": {"$ref": "#/$defs/nonNegativeInteger"}, "minLength": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "pattern": { "type": "string", "format": "regex" }, "maxItems": {"$ref": "#/$defs/nonNegativeInteger"}, "minItems": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "uniqueItems": { "type": "boolean", "default": false }, "maxContains": {"$ref": "#/$defs/nonNegativeInteger"}, "minContains": { "$ref": "#/$defs/nonNegativeInteger", "default": 1 }, "maxProperties": {"$ref": "#/$defs/nonNegativeInteger"}, "minProperties": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/$defs/stringArray"}, "dependentRequired": { "type": "object", "additionalProperties": { "$ref": "#/$defs/stringArray" } } }, "$defs": { "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 }, "simpleTypes": { "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": [] } } } ================================================ FILE: lib/refs/json-schema-2020-12/schema.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/applicator": true, "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, "https://json-schema.org/draft/2020-12/vocab/validation": true, "https://json-schema.org/draft/2020-12/vocab/meta-data": true, "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, "https://json-schema.org/draft/2020-12/vocab/content": true }, "$dynamicAnchor": "meta", "title": "Core and Validation specifications meta-schema", "allOf": [ {"$ref": "meta/core"}, {"$ref": "meta/applicator"}, {"$ref": "meta/unevaluated"}, {"$ref": "meta/validation"}, {"$ref": "meta/meta-data"}, {"$ref": "meta/format-annotation"}, {"$ref": "meta/content"} ], "type": ["object", "boolean"], "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", "properties": { "definitions": { "$comment": "\"definitions\" has been replaced by \"$defs\".", "type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "deprecated": true, "default": {} }, "dependencies": { "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", "type": "object", "additionalProperties": { "anyOf": [{"$dynamicRef": "#meta"}, {"$ref": "meta/validation#/$defs/stringArray"}] }, "deprecated": true, "default": {} }, "$recursiveAnchor": { "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", "$ref": "meta/core#/$defs/anchorString", "deprecated": true }, "$recursiveRef": { "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", "$ref": "meta/core#/$defs/uriReferenceString", "deprecated": true } } } ================================================ FILE: lib/refs/json-schema-draft-06.json ================================================ { "$schema": "http://json-schema.org/draft-06/schema#", "$id": "http://json-schema.org/draft-06/schema#", "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": {"$ref": "#"} }, "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] }, "simpleTypes": { "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": [] } }, "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri-reference" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "examples": { "type": "array", "items": {} }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": {"$ref": "#/definitions/nonNegativeInteger"}, "minLength": {"$ref": "#/definitions/nonNegativeIntegerDefault0"}, "pattern": { "type": "string", "format": "regex" }, "additionalItems": {"$ref": "#"}, "items": { "anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/schemaArray"}], "default": {} }, "maxItems": {"$ref": "#/definitions/nonNegativeInteger"}, "minItems": {"$ref": "#/definitions/nonNegativeIntegerDefault0"}, "uniqueItems": { "type": "boolean", "default": false }, "contains": {"$ref": "#"}, "maxProperties": {"$ref": "#/definitions/nonNegativeInteger"}, "minProperties": {"$ref": "#/definitions/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/definitions/stringArray"}, "additionalProperties": {"$ref": "#"}, "definitions": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "properties": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/stringArray"}] } }, "propertyNames": {"$ref": "#"}, "const": {}, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ {"$ref": "#/definitions/simpleTypes"}, { "type": "array", "items": {"$ref": "#/definitions/simpleTypes"}, "minItems": 1, "uniqueItems": true } ] }, "format": {"type": "string"}, "allOf": {"$ref": "#/definitions/schemaArray"}, "anyOf": {"$ref": "#/definitions/schemaArray"}, "oneOf": {"$ref": "#/definitions/schemaArray"}, "not": {"$ref": "#"} }, "default": {} } ================================================ FILE: lib/refs/json-schema-draft-07.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://json-schema.org/draft-07/schema#", "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": {"$ref": "#"} }, "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] }, "simpleTypes": { "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": [] } }, "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri-reference" }, "$comment": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "readOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": {"$ref": "#/definitions/nonNegativeInteger"}, "minLength": {"$ref": "#/definitions/nonNegativeIntegerDefault0"}, "pattern": { "type": "string", "format": "regex" }, "additionalItems": {"$ref": "#"}, "items": { "anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/schemaArray"}], "default": true }, "maxItems": {"$ref": "#/definitions/nonNegativeInteger"}, "minItems": {"$ref": "#/definitions/nonNegativeIntegerDefault0"}, "uniqueItems": { "type": "boolean", "default": false }, "contains": {"$ref": "#"}, "maxProperties": {"$ref": "#/definitions/nonNegativeInteger"}, "minProperties": {"$ref": "#/definitions/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/definitions/stringArray"}, "additionalProperties": {"$ref": "#"}, "definitions": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "properties": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": {"$ref": "#"}, "propertyNames": {"format": "regex"}, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/stringArray"}] } }, "propertyNames": {"$ref": "#"}, "const": true, "enum": { "type": "array", "items": true, "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ {"$ref": "#/definitions/simpleTypes"}, { "type": "array", "items": {"$ref": "#/definitions/simpleTypes"}, "minItems": 1, "uniqueItems": true } ] }, "format": {"type": "string"}, "contentMediaType": {"type": "string"}, "contentEncoding": {"type": "string"}, "if": {"$ref": "#"}, "then": {"$ref": "#"}, "else": {"$ref": "#"}, "allOf": {"$ref": "#/definitions/schemaArray"}, "anyOf": {"$ref": "#/definitions/schemaArray"}, "oneOf": {"$ref": "#/definitions/schemaArray"}, "not": {"$ref": "#"} }, "default": true } ================================================ FILE: lib/refs/json-schema-secure.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#", "title": "Meta-schema for the security assessment of JSON Schemas", "description": "If a JSON AnySchema fails validation against this meta-schema, it may be unsafe to validate untrusted data", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": {"$ref": "#"} } }, "dependencies": { "patternProperties": { "description": "prevent slow validation of large property names", "required": ["propertyNames"], "properties": { "propertyNames": { "required": ["maxLength"] } } }, "uniqueItems": { "description": "prevent slow validation of large non-scalar arrays", "if": { "properties": { "uniqueItems": {"const": true}, "items": { "properties": { "type": { "anyOf": [ { "enum": ["object", "array"] }, { "type": "array", "contains": {"enum": ["object", "array"]} } ] } } } } }, "then": { "required": ["maxItems"] } }, "pattern": { "description": "prevent slow pattern matching of large strings", "required": ["maxLength"] }, "format": { "description": "prevent slow format validation of large strings", "required": ["maxLength"] } }, "properties": { "additionalItems": {"$ref": "#"}, "additionalProperties": {"$ref": "#"}, "dependencies": { "additionalProperties": { "anyOf": [{"type": "array"}, {"$ref": "#"}] } }, "items": { "anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/schemaArray"}] }, "definitions": { "additionalProperties": {"$ref": "#"} }, "patternProperties": { "additionalProperties": {"$ref": "#"} }, "properties": { "additionalProperties": {"$ref": "#"} }, "if": {"$ref": "#"}, "then": {"$ref": "#"}, "else": {"$ref": "#"}, "allOf": {"$ref": "#/definitions/schemaArray"}, "anyOf": {"$ref": "#/definitions/schemaArray"}, "oneOf": {"$ref": "#/definitions/schemaArray"}, "not": {"$ref": "#"}, "contains": {"$ref": "#"}, "propertyNames": {"$ref": "#"} } } ================================================ FILE: lib/refs/jtd-schema.ts ================================================ import {SchemaObject} from "../types" type MetaSchema = (root: boolean) => SchemaObject const shared: MetaSchema = (root) => { const sch: SchemaObject = { nullable: {type: "boolean"}, metadata: { optionalProperties: { union: {elements: {ref: "schema"}}, }, additionalProperties: true, }, } if (root) sch.definitions = {values: {ref: "schema"}} return sch } const emptyForm: MetaSchema = (root) => ({ optionalProperties: shared(root), }) const refForm: MetaSchema = (root) => ({ properties: { ref: {type: "string"}, }, optionalProperties: shared(root), }) const typeForm: MetaSchema = (root) => ({ properties: { type: { enum: [ "boolean", "timestamp", "string", "float32", "float64", "int8", "uint8", "int16", "uint16", "int32", "uint32", ], }, }, optionalProperties: shared(root), }) const enumForm: MetaSchema = (root) => ({ properties: { enum: {elements: {type: "string"}}, }, optionalProperties: shared(root), }) const elementsForm: MetaSchema = (root) => ({ properties: { elements: {ref: "schema"}, }, optionalProperties: shared(root), }) const propertiesForm: MetaSchema = (root) => ({ properties: { properties: {values: {ref: "schema"}}, }, optionalProperties: { optionalProperties: {values: {ref: "schema"}}, additionalProperties: {type: "boolean"}, ...shared(root), }, }) const optionalPropertiesForm: MetaSchema = (root) => ({ properties: { optionalProperties: {values: {ref: "schema"}}, }, optionalProperties: { additionalProperties: {type: "boolean"}, ...shared(root), }, }) const discriminatorForm: MetaSchema = (root) => ({ properties: { discriminator: {type: "string"}, mapping: { values: { metadata: { union: [propertiesForm(false), optionalPropertiesForm(false)], }, }, }, }, optionalProperties: shared(root), }) const valuesForm: MetaSchema = (root) => ({ properties: { values: {ref: "schema"}, }, optionalProperties: shared(root), }) const schema: MetaSchema = (root) => ({ metadata: { union: [ emptyForm, refForm, typeForm, enumForm, elementsForm, propertiesForm, optionalPropertiesForm, discriminatorForm, valuesForm, ].map((s) => s(root)), }, }) const jtdMetaSchema: SchemaObject = { definitions: { schema: schema(false), }, ...schema(true), } export default jtdMetaSchema ================================================ FILE: lib/runtime/equal.ts ================================================ // https://github.com/ajv-validator/ajv/issues/889 import * as equal from "fast-deep-equal" type Equal = typeof equal & {code: string} ;(equal as Equal).code = 'require("ajv/dist/runtime/equal").default' export default equal as Equal ================================================ FILE: lib/runtime/parseJson.ts ================================================ const rxParseJson = /position\s(\d+)(?: \(line \d+ column \d+\))?$/ export function parseJson(s: string, pos: number): unknown { let endPos: number | undefined parseJson.message = undefined let matches: RegExpExecArray | null if (pos) s = s.slice(pos) try { parseJson.position = pos + s.length return JSON.parse(s) } catch (e) { matches = rxParseJson.exec((e as Error).message) if (!matches) { parseJson.message = "unexpected end" return undefined } endPos = +matches[1] const c = s[endPos] s = s.slice(0, endPos) parseJson.position = pos + endPos try { return JSON.parse(s) } catch (e1) { parseJson.message = `unexpected token ${c}` return undefined } } } parseJson.message = undefined as string | undefined parseJson.position = 0 as number parseJson.code = 'require("ajv/dist/runtime/parseJson").parseJson' export function parseJsonNumber(s: string, pos: number, maxDigits?: number): number | undefined { let numStr = "" let c: string parseJsonNumber.message = undefined if (s[pos] === "-") { numStr += "-" pos++ } if (s[pos] === "0") { numStr += "0" pos++ } else { if (!parseDigits(maxDigits)) { errorMessage() return undefined } } if (maxDigits) { parseJsonNumber.position = pos return +numStr } if (s[pos] === ".") { numStr += "." pos++ if (!parseDigits()) { errorMessage() return undefined } } if (((c = s[pos]), c === "e" || c === "E")) { numStr += "e" pos++ if (((c = s[pos]), c === "+" || c === "-")) { numStr += c pos++ } if (!parseDigits()) { errorMessage() return undefined } } parseJsonNumber.position = pos return +numStr function parseDigits(maxLen?: number): boolean { let digit = false while (((c = s[pos]), c >= "0" && c <= "9" && (maxLen === undefined || maxLen-- > 0))) { digit = true numStr += c pos++ } return digit } function errorMessage(): void { parseJsonNumber.position = pos parseJsonNumber.message = pos < s.length ? `unexpected token ${s[pos]}` : "unexpected end" } } parseJsonNumber.message = undefined as string | undefined parseJsonNumber.position = 0 as number parseJsonNumber.code = 'require("ajv/dist/runtime/parseJson").parseJsonNumber' const escapedChars: {[X in string]?: string} = { b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", '"': '"', "/": "/", "\\": "\\", } const CODE_A: number = "a".charCodeAt(0) const CODE_0: number = "0".charCodeAt(0) export function parseJsonString(s: string, pos: number): string | undefined { let str = "" let c: string | undefined parseJsonString.message = undefined // eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unnecessary-condition while (true) { c = s[pos++] if (c === '"') break if (c === "\\") { c = s[pos] if (c in escapedChars) { str += escapedChars[c] pos++ } else if (c === "u") { pos++ let count = 4 let code = 0 while (count--) { code <<= 4 c = s[pos] // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (c === undefined) { errorMessage("unexpected end") return undefined } c = c.toLowerCase() if (c >= "a" && c <= "f") { code += c.charCodeAt(0) - CODE_A + 10 } else if (c >= "0" && c <= "9") { code += c.charCodeAt(0) - CODE_0 } else { errorMessage(`unexpected token ${c}`) return undefined } pos++ } str += String.fromCharCode(code) } else { errorMessage(`unexpected token ${c}`) return undefined } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (c === undefined) { errorMessage("unexpected end") return undefined } else { if (c.charCodeAt(0) >= 0x20) { str += c } else { errorMessage(`unexpected token ${c}`) return undefined } } } parseJsonString.position = pos return str function errorMessage(msg: string): void { parseJsonString.position = pos parseJsonString.message = msg } } parseJsonString.message = undefined as string | undefined parseJsonString.position = 0 as number parseJsonString.code = 'require("ajv/dist/runtime/parseJson").parseJsonString' ================================================ FILE: lib/runtime/quote.ts ================================================ const rxEscapable = // eslint-disable-next-line no-control-regex, no-misleading-character-class /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g const escaped: {[K in string]?: string} = { "\b": "\\b", "\t": "\\t", "\n": "\\n", "\f": "\\f", "\r": "\\r", '"': '\\"', "\\": "\\\\", } export default function quote(s: string): string { rxEscapable.lastIndex = 0 return ( '"' + (rxEscapable.test(s) ? s.replace(rxEscapable, (a) => { const c = escaped[a] return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4) }) : s) + '"' ) } quote.code = 'require("ajv/dist/runtime/quote").default' ================================================ FILE: lib/runtime/re2.ts ================================================ import * as re2 from "re2" type Re2 = typeof re2 & {code: string} ;(re2 as Re2).code = 'require("ajv/dist/runtime/re2").default' export default re2 as Re2 ================================================ FILE: lib/runtime/timestamp.ts ================================================ const DT_SEPARATOR = /t|\s/i const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/ const TIME = /^(\d\d):(\d\d):(\d\d)(?:\.\d+)?(?:z|([+-]\d\d)(?::?(\d\d))?)$/i const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] export default function validTimestamp(str: string, allowDate: boolean): boolean { // http://tools.ietf.org/html/rfc3339#section-5.6 const dt: string[] = str.split(DT_SEPARATOR) return ( (dt.length === 2 && validDate(dt[0]) && validTime(dt[1])) || (allowDate && dt.length === 1 && validDate(dt[0])) ) } function validDate(str: string): boolean { const matches: string[] | null = DATE.exec(str) if (!matches) return false const y: number = +matches[1] const m: number = +matches[2] const d: number = +matches[3] return ( m >= 1 && m <= 12 && d >= 1 && (d <= DAYS[m] || // leap year: https://tools.ietf.org/html/rfc3339#appendix-C (m === 2 && d === 29 && (y % 100 === 0 ? y % 400 === 0 : y % 4 === 0))) ) } function validTime(str: string): boolean { const matches: string[] | null = TIME.exec(str) if (!matches) return false const hr: number = +matches[1] const min: number = +matches[2] const sec: number = +matches[3] const tzH: number = +(matches[4] || 0) const tzM: number = +(matches[5] || 0) return ( (hr <= 23 && min <= 59 && sec <= 59) || // leap second (hr - tzH === 23 && min - tzM === 59 && sec === 60) ) } validTimestamp.code = 'require("ajv/dist/runtime/timestamp").default' ================================================ FILE: lib/runtime/ucs2length.ts ================================================ // https://mathiasbynens.be/notes/javascript-encoding // https://github.com/bestiejs/punycode.js - punycode.ucs2.decode export default function ucs2length(str: string): number { const len = str.length let length = 0 let pos = 0 let value: number while (pos < len) { length++ value = str.charCodeAt(pos++) if (value >= 0xd800 && value <= 0xdbff && pos < len) { // high surrogate, and there is a next character value = str.charCodeAt(pos) if ((value & 0xfc00) === 0xdc00) pos++ // low surrogate } } return length } ucs2length.code = 'require("ajv/dist/runtime/ucs2length").default' ================================================ FILE: lib/runtime/uri.ts ================================================ import * as uri from "fast-uri" type URI = typeof uri & {code: string} ;(uri as URI).code = 'require("ajv/dist/runtime/uri").default' export default uri as URI ================================================ FILE: lib/runtime/validation_error.ts ================================================ import type {ErrorObject} from "../types" export default class ValidationError extends Error { readonly errors: Partial[] readonly ajv: true readonly validation: true constructor(errors: Partial[]) { super("validation failed") this.errors = errors this.ajv = this.validation = true } } ================================================ FILE: lib/standalone/index.ts ================================================ import type AjvCore from "../core" import type {AnyValidateFunction, SourceCode} from "../types" import type {SchemaEnv} from "../compile" import {UsedScopeValues, UsedValueState, ValueScopeName, varKinds} from "../compile/codegen/scope" import {_, nil, _Code, Code, getProperty, getEsmExportName} from "../compile/codegen/code" function standaloneCode( ajv: AjvCore, refsOrFunc?: {[K in string]?: string} | AnyValidateFunction ): string { if (!ajv.opts.code.source) { throw new Error("moduleCode: ajv instance must have code.source option") } const {_n} = ajv.scope.opts return typeof refsOrFunc == "function" ? funcExportCode(refsOrFunc.source) : refsOrFunc !== undefined ? multiExportsCode(refsOrFunc, getValidate) : multiExportsCode(ajv.schemas, (sch) => sch.meta ? undefined : ajv.compile(sch.schema) ) function getValidate(id: string): AnyValidateFunction { const v = ajv.getSchema(id) if (!v) throw new Error(`moduleCode: no schema with id ${id}`) return v } function funcExportCode(source?: SourceCode): string { const usedValues: UsedScopeValues = {} const n = source?.validateName const vCode = validateCode(usedValues, source) if (ajv.opts.code.esm) { // Always do named export as `validate` rather than the variable `n` which is `validateXX` for known export value return `"use strict";${_n}export const validate = ${n};${_n}export default ${n};${_n}${vCode}` } return `"use strict";${_n}module.exports = ${n};${_n}module.exports.default = ${n};${_n}${vCode}` } function multiExportsCode( schemas: {[K in string]?: T}, getValidateFunc: (schOrId: T) => AnyValidateFunction | undefined ): string { const usedValues: UsedScopeValues = {} let code = _`"use strict";` for (const name in schemas) { const v = getValidateFunc(schemas[name] as T) if (v) { const vCode = validateCode(usedValues, v.source) const exportSyntax = ajv.opts.code.esm ? _`export const ${getEsmExportName(name)}` : _`exports${getProperty(name)}` code = _`${code}${_n}${exportSyntax} = ${v.source?.validateName};${_n}${vCode}` } } return `${code}` } function validateCode(usedValues: UsedScopeValues, s?: SourceCode): Code { if (!s) throw new Error('moduleCode: function does not have "source" property') if (usedState(s.validateName) === UsedValueState.Completed) return nil setUsedState(s.validateName, UsedValueState.Started) const scopeCode = ajv.scope.scopeCode(s.scopeValues, usedValues, refValidateCode) const code = new _Code(`${scopeCode}${_n}${s.validateCode}`) return s.evaluated ? _`${code}${s.validateName}.evaluated = ${s.evaluated};${_n}` : code function refValidateCode(n: ValueScopeName): Code | undefined { const vRef = n.value?.ref if (n.prefix === "validate" && typeof vRef == "function") { const v = vRef as AnyValidateFunction return validateCode(usedValues, v.source) } else if ((n.prefix === "root" || n.prefix === "wrapper") && typeof vRef == "object") { const {validate, validateName} = vRef as SchemaEnv if (!validateName) throw new Error("ajv internal error") const def = ajv.opts.code.es5 ? varKinds.var : varKinds.const const wrapper = _`${def} ${n} = {validate: ${validateName}};` if (usedState(validateName) === UsedValueState.Started) return wrapper const vCode = validateCode(usedValues, validate?.source) return _`${wrapper}${_n}${vCode}` } return undefined } function usedState(name: ValueScopeName): UsedValueState | undefined { return usedValues[name.prefix]?.get(name) } function setUsedState(name: ValueScopeName, state: UsedValueState): void { const {prefix} = name const names = (usedValues[prefix] = usedValues[prefix] || new Map()) names.set(name, state) } } } module.exports = exports = standaloneCode Object.defineProperty(exports, "__esModule", {value: true}) export default standaloneCode ================================================ FILE: lib/standalone/instance.ts ================================================ import Ajv, {AnySchema, AnyValidateFunction, ErrorObject} from "../core" import standaloneCode from "." import * as requireFromString from "require-from-string" export default class AjvPack { errors?: ErrorObject[] | null // errors from the last validation constructor(readonly ajv: Ajv) {} validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise { return Ajv.prototype.validate.call(this, schemaKeyRef, data) } compile(schema: AnySchema, meta?: boolean): AnyValidateFunction { return this.getStandalone(this.ajv.compile(schema, meta)) } getSchema(keyRef: string): AnyValidateFunction | undefined { const v = this.ajv.getSchema(keyRef) if (!v) return undefined return this.getStandalone(v) } private getStandalone(v: AnyValidateFunction): AnyValidateFunction { return requireFromString(standaloneCode(this.ajv, v)) as AnyValidateFunction } addSchema(...args: Parameters): AjvPack { this.ajv.addSchema.call(this.ajv, ...args) return this } addKeyword(...args: Parameters): AjvPack { this.ajv.addKeyword.call(this.ajv, ...args) return this } } ================================================ FILE: lib/types/index.ts ================================================ import {URIComponent} from "fast-uri" import type {CodeGen, Code, Name, ScopeValueSets, ValueScopeName} from "../compile/codegen" import type {SchemaEnv, SchemaCxt, SchemaObjCxt} from "../compile" import type {JSONType} from "../compile/rules" import type {KeywordCxt} from "../compile/validate" import type Ajv from "../core" interface _SchemaObject { id?: string $id?: string $schema?: string [x: string]: any // TODO } export interface SchemaObject extends _SchemaObject { id?: string $id?: string $schema?: string $async?: false [x: string]: any // TODO } export interface AsyncSchema extends _SchemaObject { $async: true } export type AnySchemaObject = SchemaObject | AsyncSchema export type Schema = SchemaObject | boolean export type AnySchema = Schema | AsyncSchema export type SchemaMap = {[Key in string]?: AnySchema} export interface SourceCode { validateName: ValueScopeName validateCode: string scopeValues: ScopeValueSets evaluated?: Code } export interface DataValidationCxt { instancePath: string parentData: {[K in T]: any} // object or array parentDataProperty: T // string or number rootData: Record | any[] dynamicAnchors: {[Ref in string]?: ValidateFunction} } export interface ValidateFunction { // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents (this: Ajv | any, data: any, dataCxt?: DataValidationCxt): data is T errors?: null | ErrorObject[] evaluated?: Evaluated schema: AnySchema schemaEnv: SchemaEnv source?: SourceCode } export interface JTDParser { (json: string): T | undefined message?: string position?: number } export type EvaluatedProperties = {[K in string]?: true} | true export type EvaluatedItems = number | true export interface Evaluated { // determined at compile time if staticProps/Items is true props?: EvaluatedProperties items?: EvaluatedItems // whether props/items determined at compile time dynamicProps: boolean dynamicItems: boolean } export interface AsyncValidateFunction extends ValidateFunction { (...args: Parameters>): Promise $async: true } export type AnyValidateFunction = ValidateFunction | AsyncValidateFunction export interface ErrorObject, S = unknown> { keyword: K instancePath: string schemaPath: string params: P // Added to validation errors of "propertyNames" keyword schema propertyName?: string // Excluded if option `messages` set to false. message?: string // These are added with the `verbose` option. schema?: S parentSchema?: AnySchemaObject data?: unknown } export type ErrorNoParams = ErrorObject, S> interface _KeywordDef { keyword: string | string[] type?: JSONType | JSONType[] // data types that keyword applies to schemaType?: JSONType | JSONType[] // allowed type(s) of keyword value in the schema allowUndefined?: boolean // used for keywords that can be invoked by other keywords, not being present in the schema $data?: boolean // keyword supports [$data reference](../../docs/guide/combining-schemas.md#data-reference) implements?: string[] // other schema keywords that this keyword implements before?: string // keyword should be executed before this keyword (should be applicable to the same type) post?: boolean // keyword should be executed after other keywords without post flag metaSchema?: AnySchemaObject // meta-schema for keyword schema value - it is better to use schemaType where applicable validateSchema?: AnyValidateFunction // compiled keyword metaSchema - should not be passed dependencies?: string[] // keywords that must be present in the same schema error?: KeywordErrorDefinition $dataError?: KeywordErrorDefinition } export interface CodeKeywordDefinition extends _KeywordDef { code: (cxt: KeywordCxt, ruleType?: string) => void trackErrors?: boolean } export type MacroKeywordFunc = ( schema: any, parentSchema: AnySchemaObject, it: SchemaCxt ) => AnySchema export type CompileKeywordFunc = ( schema: any, parentSchema: AnySchemaObject, it: SchemaObjCxt ) => DataValidateFunction export interface DataValidateFunction { (...args: Parameters): boolean | Promise errors?: Partial[] } export interface SchemaValidateFunction { ( schema: any, data: any, parentSchema?: AnySchemaObject, dataCxt?: DataValidationCxt ): boolean | Promise errors?: Partial[] } export interface FuncKeywordDefinition extends _KeywordDef { validate?: SchemaValidateFunction | DataValidateFunction compile?: CompileKeywordFunc // schema: false makes validate not to expect schema (DataValidateFunction) schema?: boolean // requires "validate" modifying?: boolean async?: boolean valid?: boolean errors?: boolean | "full" } export interface MacroKeywordDefinition extends FuncKeywordDefinition { macro: MacroKeywordFunc } export type KeywordDefinition = | CodeKeywordDefinition | FuncKeywordDefinition | MacroKeywordDefinition export type AddedKeywordDefinition = KeywordDefinition & { type: JSONType[] schemaType: JSONType[] } export interface KeywordErrorDefinition { message: string | Code | ((cxt: KeywordErrorCxt) => string | Code) params?: Code | ((cxt: KeywordErrorCxt) => Code) } export type Vocabulary = (KeywordDefinition | string)[] export interface KeywordErrorCxt { gen: CodeGen keyword: string data: Name $data?: string | false schema: any // TODO parentSchema?: AnySchemaObject schemaCode: Code | number | boolean schemaValue: Code | number | boolean schemaType?: JSONType[] errsCount?: Name params: KeywordCxtParams it: SchemaCxt } export type KeywordCxtParams = {[P in string]?: Code | string | number} export type FormatValidator = (data: T) => boolean export type FormatCompare = (data1: T, data2: T) => number | undefined export type AsyncFormatValidator = (data: T) => Promise export interface FormatDefinition { type?: T extends string ? "string" | undefined : "number" validate: FormatValidator | (T extends string ? string | RegExp : never) async?: false | undefined compare?: FormatCompare } export interface AsyncFormatDefinition { type?: T extends string ? "string" | undefined : "number" validate: AsyncFormatValidator async: true compare?: FormatCompare } export type AddedFormat = | true | RegExp | FormatValidator | FormatDefinition | FormatDefinition | AsyncFormatDefinition | AsyncFormatDefinition export type Format = AddedFormat | string export interface RegExpEngine { (pattern: string, u: string): RegExpLike code: string } export interface RegExpLike { test: (s: string) => boolean } export interface UriResolver { parse(uri: string): URIComponent resolve(base: string, path: string): string serialize(component: URIComponent): string } ================================================ FILE: lib/types/json-schema.ts ================================================ /* eslint-disable @typescript-eslint/no-empty-interface */ type StrictNullChecksWrapper = undefined extends null ? `strictNullChecks must be true in tsconfig to use ${Name}` : Type type UnionToIntersection = (U extends any ? (_: U) => void : never) extends (_: infer I) => void ? I : never export type SomeJSONSchema = UncheckedJSONSchemaType type UncheckedPartialSchema = Partial> export type PartialSchema = StrictNullChecksWrapper<"PartialSchema", UncheckedPartialSchema> type JSONType = IsPartial extends true ? T | undefined : T interface NumberKeywords { minimum?: number maximum?: number exclusiveMinimum?: number exclusiveMaximum?: number multipleOf?: number format?: string } interface StringKeywords { minLength?: number maxLength?: number pattern?: string format?: string } type UncheckedJSONSchemaType = ( | // these two unions allow arbitrary unions of types { anyOf: readonly UncheckedJSONSchemaType[] } | { oneOf: readonly UncheckedJSONSchemaType[] } // this union allows for { type: (primitive)[] } style schemas | ({ type: readonly (T extends number ? JSONType<"number" | "integer", IsPartial> : T extends string ? JSONType<"string", IsPartial> : T extends boolean ? JSONType<"boolean", IsPartial> : never)[] } & UnionToIntersection< T extends number ? NumberKeywords : T extends string ? StringKeywords : T extends boolean ? // eslint-disable-next-line @typescript-eslint/ban-types {} : never >) // this covers "normal" types; it's last so typescript looks to it first for errors | ((T extends number ? { type: JSONType<"number" | "integer", IsPartial> } & NumberKeywords : T extends string ? { type: JSONType<"string", IsPartial> } & StringKeywords : T extends boolean ? { type: JSONType<"boolean", IsPartial> } : T extends readonly [any, ...any[]] ? { // JSON AnySchema for tuple type: JSONType<"array", IsPartial> items: { readonly [K in keyof T]-?: UncheckedJSONSchemaType & Nullable } & {length: T["length"]} minItems: T["length"] } & ({maxItems: T["length"]} | {additionalItems: false}) : T extends readonly any[] ? { type: JSONType<"array", IsPartial> items: UncheckedJSONSchemaType contains?: UncheckedPartialSchema minItems?: number maxItems?: number minContains?: number maxContains?: number uniqueItems?: true additionalItems?: never } : T extends Record ? { // JSON AnySchema for records and dictionaries // "required" is not optional because it is often forgotten // "properties" are optional for more concise dictionary schemas // "patternProperties" and can be only used with interfaces that have string index type: JSONType<"object", IsPartial> additionalProperties?: boolean | UncheckedJSONSchemaType unevaluatedProperties?: boolean | UncheckedJSONSchemaType properties?: IsPartial extends true ? Partial> : UncheckedPropertiesSchema patternProperties?: Record> propertyNames?: Omit, "type"> & {type?: "string"} dependencies?: {[K in keyof T]?: readonly (keyof T)[] | UncheckedPartialSchema} dependentRequired?: {[K in keyof T]?: readonly (keyof T)[]} dependentSchemas?: {[K in keyof T]?: UncheckedPartialSchema} minProperties?: number maxProperties?: number } & (IsPartial extends true // "required" is not necessary if it's a non-partial type with no required keys // are listed it only asserts that optional cannot be listed. // "required" type does not guarantee that all required properties ? {required: readonly (keyof T)[]} : [UncheckedRequiredMembers] extends [never] ? {required?: readonly UncheckedRequiredMembers[]} : {required: readonly UncheckedRequiredMembers[]}) : T extends null ? { type: JSONType<"null", IsPartial> nullable: true } : never) & { allOf?: readonly UncheckedPartialSchema[] anyOf?: readonly UncheckedPartialSchema[] oneOf?: readonly UncheckedPartialSchema[] if?: UncheckedPartialSchema then?: UncheckedPartialSchema else?: UncheckedPartialSchema not?: UncheckedPartialSchema }) ) & { [keyword: string]: any $id?: string $ref?: string $defs?: Record> definitions?: Record> } export type JSONSchemaType = StrictNullChecksWrapper< "JSONSchemaType", UncheckedJSONSchemaType > type Known = | {[key: string]: Known} | [Known, ...Known[]] | Known[] | number | string | boolean | null type UncheckedPropertiesSchema = { [K in keyof T]-?: (UncheckedJSONSchemaType & Nullable) | {$ref: string} } export type PropertiesSchema = StrictNullChecksWrapper< "PropertiesSchema", UncheckedPropertiesSchema > type UncheckedRequiredMembers = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T] export type RequiredMembers = StrictNullChecksWrapper< "RequiredMembers", UncheckedRequiredMembers > type Nullable = undefined extends T ? { nullable: true const?: null // any non-null value would fail `const: null`, `null` would fail any other value in const enum?: readonly (T | null)[] // `null` must be explicitly included in "enum" for `null` to pass default?: T | null } : { nullable?: false const?: T enum?: readonly T[] default?: T } ================================================ FILE: lib/types/jtd-schema.ts ================================================ /** numeric strings */ type NumberType = "float32" | "float64" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" /** string strings */ type StringType = "string" | "timestamp" /** Generic JTD Schema without inference of the represented type */ export type SomeJTDSchemaType = ( | // ref {ref: string} // primitives | {type: NumberType | StringType | "boolean"} // enum | {enum: string[]} // elements | {elements: SomeJTDSchemaType} // values | {values: SomeJTDSchemaType} // properties | { properties: Record optionalProperties?: Record additionalProperties?: boolean } | { properties?: Record optionalProperties: Record additionalProperties?: boolean } // discriminator | {discriminator: string; mapping: Record} // empty // NOTE see the end of // https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492 // eslint-disable-next-line @typescript-eslint/ban-types | {} ) & { nullable?: boolean metadata?: Record definitions?: Record } /** required keys of an object, not undefined */ type RequiredKeys = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T] /** optional or undifined-able keys of an object */ type OptionalKeys = { [K in keyof T]-?: undefined extends T[K] ? K : never }[keyof T] /** type is true if T is a union type */ type IsUnion_ = false extends ( T extends unknown ? ([U] extends [T] ? false : true) : never ) ? false : true type IsUnion = IsUnion_ /** type is true if T is identically E */ type TypeEquality = [T] extends [E] ? ([E] extends [T] ? true : false) : false /** type is true if T or null is identically E or null*/ type NullTypeEquality = TypeEquality /** gets only the string literals of a type or null if a type isn't a string literal */ type EnumString = [T] extends [never] ? null : T extends string ? string extends T ? null : T : null /** true if type is a union of string literals */ type IsEnum = null extends EnumString ? false : true /** true only if all types are array types (not tuples) */ // NOTE relies on the fact that tuples don't have an index at 0.5, but arrays // have an index at every number type IsElements = false extends IsUnion ? [T] extends [readonly unknown[]] ? undefined extends T[0.5] ? false : true : false : false /** true if the the type is a values type */ type IsValues = false extends IsUnion ? TypeEquality : false /** true if type is a properties type and Union is false, or type is a discriminator type and Union is true */ type IsRecord = Union extends IsUnion ? null extends EnumString ? false : true : false /** true if type represents an empty record */ type IsEmptyRecord = [T] extends [Record] ? [T] extends [never] ? false : true : false /** actual schema */ export type JTDSchemaType = Record> = ( | // refs - where null wasn't specified, must match exactly (null extends EnumString ? never : | ({[K in keyof D]: [T] extends [D[K]] ? {ref: K} : never}[keyof D] & {nullable?: false}) // nulled refs - if ref is nullable and nullable is specified, then it can // match either null or non-null definitions | (null extends T ? { [K in keyof D]: [Exclude] extends [Exclude] ? {ref: K} : never }[keyof D] & {nullable: true} : never)) // empty - empty schemas also treat nullable differently in that it's now fully ignored | (unknown extends T ? {nullable?: boolean} : never) // all other types // numbers - only accepts the type number | ((true extends NullTypeEquality ? {type: NumberType} : // booleans - accepts the type boolean true extends NullTypeEquality ? {type: "boolean"} : // strings - only accepts the type string true extends NullTypeEquality ? {type: StringType} : // strings - only accepts the type Date true extends NullTypeEquality ? {type: "timestamp"} : // enums - only accepts union of string literals // TODO we can't actually check that everything in the union was specified true extends IsEnum> ? {enum: EnumString>[]} : // arrays - only accepts arrays, could be array of unions to be resolved later true extends IsElements> ? T extends readonly (infer E)[] ? { elements: JTDSchemaType } : never : // empty properties true extends IsEmptyRecord> ? | {properties: Record; optionalProperties?: Record} | {optionalProperties: Record} : // values true extends IsValues> ? T extends Record ? { values: JTDSchemaType } : never : // properties true extends IsRecord, false> ? ([RequiredKeys>] extends [never] ? { properties?: Record } : { properties: {[K in RequiredKeys]: JTDSchemaType} }) & ([OptionalKeys>] extends [never] ? { optionalProperties?: Record } : { optionalProperties: { [K in OptionalKeys]: JTDSchemaType, D> } }) & { additionalProperties?: boolean } : // discriminator true extends IsRecord, true> ? { [K in keyof Exclude]-?: Exclude[K] extends string ? { discriminator: K mapping: { // TODO currently allows descriminator to be present in schema [M in Exclude[K]]: JTDSchemaType< Omit ? T : never, K>, D > } } : never }[keyof Exclude] : never) & (null extends T ? { nullable: true } : {nullable?: false})) ) & { // extra properties metadata?: Record // TODO these should only be allowed at the top level definitions?: {[K in keyof D]: JTDSchemaType} } type JTDDataDef> = | // ref (S extends {ref: string} ? D extends {[K in S["ref"]]: infer V} ? JTDDataDef : never : // type S extends {type: NumberType} ? number : S extends {type: "boolean"} ? boolean : S extends {type: "string"} ? string : S extends {type: "timestamp"} ? string | Date : // enum S extends {enum: readonly (infer E)[]} ? string extends E ? never : [E] extends [string] ? E : never : // elements S extends {elements: infer E} ? JTDDataDef[] : // properties S extends { properties: Record optionalProperties?: Record additionalProperties?: boolean } ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef} & { -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef< S["optionalProperties"][K], D > } & ([S["additionalProperties"]] extends [true] ? Record : unknown) : S extends { properties?: Record optionalProperties: Record additionalProperties?: boolean } ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef} & { -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef< S["optionalProperties"][K], D > } & ([S["additionalProperties"]] extends [true] ? Record : unknown) : // values S extends {values: infer V} ? Record> : // discriminator S extends {discriminator: infer M; mapping: Record} ? [M] extends [string] ? { [K in keyof S["mapping"]]: JTDDataDef & {[KM in M]: K} }[keyof S["mapping"]] : never : // empty unknown) | (S extends {nullable: true} ? null : never) export type JTDDataType = S extends {definitions: Record} ? JTDDataDef : JTDDataDef> ================================================ FILE: lib/vocabularies/applicator/additionalItems.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, not, Name} from "../../compile/codegen" import {alwaysValidSchema, checkStrictMode, Type} from "../../compile/util" export type AdditionalItemsError = ErrorObject<"additionalItems", {limit: number}, AnySchema> const error: KeywordErrorDefinition = { message: ({params: {len}}) => str`must NOT have more than ${len} items`, params: ({params: {len}}) => _`{limit: ${len}}`, } const def: CodeKeywordDefinition = { keyword: "additionalItems" as const, type: "array", schemaType: ["boolean", "object"], before: "uniqueItems", error, code(cxt: KeywordCxt) { const {parentSchema, it} = cxt const {items} = parentSchema if (!Array.isArray(items)) { checkStrictMode(it, '"additionalItems" is ignored when "items" is not an array of schemas') return } validateAdditionalItems(cxt, items) }, } export function validateAdditionalItems(cxt: KeywordCxt, items: AnySchema[]): void { const {gen, schema, data, keyword, it} = cxt it.items = true const len = gen.const("len", _`${data}.length`) if (schema === false) { cxt.setParams({len: items.length}) cxt.pass(_`${len} <= ${items.length}`) } else if (typeof schema == "object" && !alwaysValidSchema(it, schema)) { const valid = gen.var("valid", _`${len} <= ${items.length}`) // TODO var gen.if(not(valid), () => validateItems(valid)) cxt.ok(valid) } function validateItems(valid: Name): void { gen.forRange("i", items.length, len, (i) => { cxt.subschema({keyword, dataProp: i, dataPropType: Type.Num}, valid) if (!it.allErrors) gen.if(not(valid), () => gen.break()) }) } } export default def ================================================ FILE: lib/vocabularies/applicator/additionalProperties.ts ================================================ import type { CodeKeywordDefinition, AddedKeywordDefinition, ErrorObject, KeywordErrorDefinition, AnySchema, } from "../../types" import {allSchemaProperties, usePattern, isOwnProperty} from "../code" import {_, nil, or, not, Code, Name} from "../../compile/codegen" import N from "../../compile/names" import type {SubschemaArgs} from "../../compile/validate/subschema" import {alwaysValidSchema, schemaRefOrVal, Type} from "../../compile/util" export type AdditionalPropertiesError = ErrorObject< "additionalProperties", {additionalProperty: string}, AnySchema > const error: KeywordErrorDefinition = { message: "must NOT have additional properties", params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`, } const def: CodeKeywordDefinition & AddedKeywordDefinition = { keyword: "additionalProperties", type: ["object"], schemaType: ["boolean", "object"], allowUndefined: true, trackErrors: true, error, code(cxt) { const {gen, schema, parentSchema, data, errsCount, it} = cxt /* istanbul ignore if */ if (!errsCount) throw new Error("ajv implementation error") const {allErrors, opts} = it it.props = true if (opts.removeAdditional !== "all" && alwaysValidSchema(it, schema)) return const props = allSchemaProperties(parentSchema.properties) const patProps = allSchemaProperties(parentSchema.patternProperties) checkAdditionalProperties() cxt.ok(_`${errsCount} === ${N.errors}`) function checkAdditionalProperties(): void { gen.forIn("key", data, (key: Name) => { if (!props.length && !patProps.length) additionalPropertyCode(key) else gen.if(isAdditional(key), () => additionalPropertyCode(key)) }) } function isAdditional(key: Name): Code { let definedProp: Code if (props.length > 8) { // TODO maybe an option instead of hard-coded 8? const propsSchema = schemaRefOrVal(it, parentSchema.properties, "properties") definedProp = isOwnProperty(gen, propsSchema as Code, key) } else if (props.length) { definedProp = or(...props.map((p) => _`${key} === ${p}`)) } else { definedProp = nil } if (patProps.length) { definedProp = or(definedProp, ...patProps.map((p) => _`${usePattern(cxt, p)}.test(${key})`)) } return not(definedProp) } function deleteAdditional(key: Name): void { gen.code(_`delete ${data}[${key}]`) } function additionalPropertyCode(key: Name): void { if (opts.removeAdditional === "all" || (opts.removeAdditional && schema === false)) { deleteAdditional(key) return } if (schema === false) { cxt.setParams({additionalProperty: key}) cxt.error() if (!allErrors) gen.break() return } if (typeof schema == "object" && !alwaysValidSchema(it, schema)) { const valid = gen.name("valid") if (opts.removeAdditional === "failing") { applyAdditionalSchema(key, valid, false) gen.if(not(valid), () => { cxt.reset() deleteAdditional(key) }) } else { applyAdditionalSchema(key, valid) if (!allErrors) gen.if(not(valid), () => gen.break()) } } } function applyAdditionalSchema(key: Name, valid: Name, errors?: false): void { const subschema: SubschemaArgs = { keyword: "additionalProperties", dataProp: key, dataPropType: Type.Str, } if (errors === false) { Object.assign(subschema, { compositeRule: true, createErrors: false, allErrors: false, }) } cxt.subschema(subschema, valid) } }, } export default def ================================================ FILE: lib/vocabularies/applicator/allOf.ts ================================================ import type {CodeKeywordDefinition, AnySchema} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {alwaysValidSchema} from "../../compile/util" const def: CodeKeywordDefinition = { keyword: "allOf", schemaType: "array", code(cxt: KeywordCxt) { const {gen, schema, it} = cxt /* istanbul ignore if */ if (!Array.isArray(schema)) throw new Error("ajv implementation error") const valid = gen.name("valid") schema.forEach((sch: AnySchema, i: number) => { if (alwaysValidSchema(it, sch)) return const schCxt = cxt.subschema({keyword: "allOf", schemaProp: i}, valid) cxt.ok(valid) cxt.mergeEvaluated(schCxt) }) }, } export default def ================================================ FILE: lib/vocabularies/applicator/anyOf.ts ================================================ import type {CodeKeywordDefinition, ErrorNoParams, AnySchema} from "../../types" import {validateUnion} from "../code" export type AnyOfError = ErrorNoParams<"anyOf", AnySchema[]> const def: CodeKeywordDefinition = { keyword: "anyOf", schemaType: "array", trackErrors: true, code: validateUnion, error: {message: "must match a schema in anyOf"}, } export default def ================================================ FILE: lib/vocabularies/applicator/contains.ts ================================================ import type { CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, Name} from "../../compile/codegen" import {alwaysValidSchema, checkStrictMode, Type} from "../../compile/util" export type ContainsError = ErrorObject< "contains", {minContains: number; maxContains?: number}, AnySchema > const error: KeywordErrorDefinition = { message: ({params: {min, max}}) => max === undefined ? str`must contain at least ${min} valid item(s)` : str`must contain at least ${min} and no more than ${max} valid item(s)`, params: ({params: {min, max}}) => max === undefined ? _`{minContains: ${min}}` : _`{minContains: ${min}, maxContains: ${max}}`, } const def: CodeKeywordDefinition = { keyword: "contains", type: "array", schemaType: ["object", "boolean"], before: "uniqueItems", trackErrors: true, error, code(cxt: KeywordCxt) { const {gen, schema, parentSchema, data, it} = cxt let min: number let max: number | undefined const {minContains, maxContains} = parentSchema if (it.opts.next) { min = minContains === undefined ? 1 : minContains max = maxContains } else { min = 1 } const len = gen.const("len", _`${data}.length`) cxt.setParams({min, max}) if (max === undefined && min === 0) { checkStrictMode(it, `"minContains" == 0 without "maxContains": "contains" keyword ignored`) return } if (max !== undefined && min > max) { checkStrictMode(it, `"minContains" > "maxContains" is always invalid`) cxt.fail() return } if (alwaysValidSchema(it, schema)) { let cond = _`${len} >= ${min}` if (max !== undefined) cond = _`${cond} && ${len} <= ${max}` cxt.pass(cond) return } it.items = true const valid = gen.name("valid") if (max === undefined && min === 1) { validateItems(valid, () => gen.if(valid, () => gen.break())) } else if (min === 0) { gen.let(valid, true) if (max !== undefined) gen.if(_`${data}.length > 0`, validateItemsWithCount) } else { gen.let(valid, false) validateItemsWithCount() } cxt.result(valid, () => cxt.reset()) function validateItemsWithCount(): void { const schValid = gen.name("_valid") const count = gen.let("count", 0) validateItems(schValid, () => gen.if(schValid, () => checkLimits(count))) } function validateItems(_valid: Name, block: () => void): void { gen.forRange("i", 0, len, (i) => { cxt.subschema( { keyword: "contains", dataProp: i, dataPropType: Type.Num, compositeRule: true, }, _valid ) block() }) } function checkLimits(count: Name): void { gen.code(_`${count}++`) if (max === undefined) { gen.if(_`${count} >= ${min}`, () => gen.assign(valid, true).break()) } else { gen.if(_`${count} > ${max}`, () => gen.assign(valid, false).break()) if (min === 1) gen.assign(valid, true) else gen.if(_`${count} >= ${min}`, () => gen.assign(valid, true)) } } }, } export default def ================================================ FILE: lib/vocabularies/applicator/dependencies.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, SchemaMap, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str} from "../../compile/codegen" import {alwaysValidSchema} from "../../compile/util" import {checkReportMissingProp, checkMissingProp, reportMissingProp, propertyInData} from "../code" export type PropertyDependencies = {[K in string]?: string[]} export interface DependenciesErrorParams { property: string missingProperty: string depsCount: number deps: string // TODO change to string[] } type SchemaDependencies = SchemaMap export type DependenciesError = ErrorObject< "dependencies", DependenciesErrorParams, {[K in string]?: string[] | AnySchema} > export const error: KeywordErrorDefinition = { message: ({params: {property, depsCount, deps}}) => { const property_ies = depsCount === 1 ? "property" : "properties" return str`must have ${property_ies} ${deps} when property ${property} is present` }, params: ({params: {property, depsCount, deps, missingProperty}}) => _`{property: ${property}, missingProperty: ${missingProperty}, depsCount: ${depsCount}, deps: ${deps}}`, // TODO change to reference } const def: CodeKeywordDefinition = { keyword: "dependencies", type: "object", schemaType: "object", error, code(cxt: KeywordCxt) { const [propDeps, schDeps] = splitDependencies(cxt) validatePropertyDeps(cxt, propDeps) validateSchemaDeps(cxt, schDeps) }, } function splitDependencies({schema}: KeywordCxt): [PropertyDependencies, SchemaDependencies] { const propertyDeps: PropertyDependencies = {} const schemaDeps: SchemaDependencies = {} for (const key in schema) { if (key === "__proto__") continue const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps deps[key] = schema[key] } return [propertyDeps, schemaDeps] } export function validatePropertyDeps( cxt: KeywordCxt, propertyDeps: {[K in string]?: string[]} = cxt.schema ): void { const {gen, data, it} = cxt if (Object.keys(propertyDeps).length === 0) return const missing = gen.let("missing") for (const prop in propertyDeps) { const deps = propertyDeps[prop] as string[] if (deps.length === 0) continue const hasProperty = propertyInData(gen, data, prop, it.opts.ownProperties) cxt.setParams({ property: prop, depsCount: deps.length, deps: deps.join(", "), }) if (it.allErrors) { gen.if(hasProperty, () => { for (const depProp of deps) { checkReportMissingProp(cxt, depProp) } }) } else { gen.if(_`${hasProperty} && (${checkMissingProp(cxt, deps, missing)})`) reportMissingProp(cxt, missing) gen.else() } } } export function validateSchemaDeps(cxt: KeywordCxt, schemaDeps: SchemaMap = cxt.schema): void { const {gen, data, keyword, it} = cxt const valid = gen.name("valid") for (const prop in schemaDeps) { if (alwaysValidSchema(it, schemaDeps[prop] as AnySchema)) continue gen.if( propertyInData(gen, data, prop, it.opts.ownProperties), () => { const schCxt = cxt.subschema({keyword, schemaProp: prop}, valid) cxt.mergeValidEvaluated(schCxt, valid) }, () => gen.var(valid, true) // TODO var ) cxt.ok(valid) } } export default def ================================================ FILE: lib/vocabularies/applicator/dependentSchemas.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import {validateSchemaDeps} from "./dependencies" const def: CodeKeywordDefinition = { keyword: "dependentSchemas", type: "object", schemaType: "object", code: (cxt) => validateSchemaDeps(cxt), } export default def ================================================ FILE: lib/vocabularies/applicator/if.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, AnySchema, } from "../../types" import type {SchemaObjCxt} from "../../compile" import type {KeywordCxt} from "../../compile/validate" import {_, str, not, Name} from "../../compile/codegen" import {alwaysValidSchema, checkStrictMode} from "../../compile/util" export type IfKeywordError = ErrorObject<"if", {failingKeyword: string}, AnySchema> const error: KeywordErrorDefinition = { message: ({params}) => str`must match "${params.ifClause}" schema`, params: ({params}) => _`{failingKeyword: ${params.ifClause}}`, } const def: CodeKeywordDefinition = { keyword: "if", schemaType: ["object", "boolean"], trackErrors: true, error, code(cxt: KeywordCxt) { const {gen, parentSchema, it} = cxt if (parentSchema.then === undefined && parentSchema.else === undefined) { checkStrictMode(it, '"if" without "then" and "else" is ignored') } const hasThen = hasSchema(it, "then") const hasElse = hasSchema(it, "else") if (!hasThen && !hasElse) return const valid = gen.let("valid", true) const schValid = gen.name("_valid") validateIf() cxt.reset() if (hasThen && hasElse) { const ifClause = gen.let("ifClause") cxt.setParams({ifClause}) gen.if(schValid, validateClause("then", ifClause), validateClause("else", ifClause)) } else if (hasThen) { gen.if(schValid, validateClause("then")) } else { gen.if(not(schValid), validateClause("else")) } cxt.pass(valid, () => cxt.error(true)) function validateIf(): void { const schCxt = cxt.subschema( { keyword: "if", compositeRule: true, createErrors: false, allErrors: false, }, schValid ) cxt.mergeEvaluated(schCxt) } function validateClause(keyword: string, ifClause?: Name): () => void { return () => { const schCxt = cxt.subschema({keyword}, schValid) gen.assign(valid, schValid) cxt.mergeValidEvaluated(schCxt, valid) if (ifClause) gen.assign(ifClause, _`${keyword}`) else cxt.setParams({ifClause: keyword}) } } }, } function hasSchema(it: SchemaObjCxt, keyword: string): boolean { const schema = it.schema[keyword] return schema !== undefined && !alwaysValidSchema(it, schema) } export default def ================================================ FILE: lib/vocabularies/applicator/index.ts ================================================ import type {ErrorNoParams, Vocabulary} from "../../types" import additionalItems, {AdditionalItemsError} from "./additionalItems" import prefixItems from "./prefixItems" import items from "./items" import items2020, {ItemsError} from "./items2020" import contains, {ContainsError} from "./contains" import dependencies, {DependenciesError} from "./dependencies" import propertyNames, {PropertyNamesError} from "./propertyNames" import additionalProperties, {AdditionalPropertiesError} from "./additionalProperties" import properties from "./properties" import patternProperties from "./patternProperties" import notKeyword, {NotKeywordError} from "./not" import anyOf, {AnyOfError} from "./anyOf" import oneOf, {OneOfError} from "./oneOf" import allOf from "./allOf" import ifKeyword, {IfKeywordError} from "./if" import thenElse from "./thenElse" export default function getApplicator(draft2020 = false): Vocabulary { const applicator = [ // any notKeyword, anyOf, oneOf, allOf, ifKeyword, thenElse, // object propertyNames, additionalProperties, dependencies, properties, patternProperties, ] // array if (draft2020) applicator.push(prefixItems, items2020) else applicator.push(additionalItems, items) applicator.push(contains) return applicator } export type ApplicatorKeywordError = | ErrorNoParams<"false schema"> | AdditionalItemsError | ItemsError | ContainsError | AdditionalPropertiesError | DependenciesError | IfKeywordError | AnyOfError | OneOfError | NotKeywordError | PropertyNamesError ================================================ FILE: lib/vocabularies/applicator/items.ts ================================================ import type {CodeKeywordDefinition, AnySchema, AnySchemaObject} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_} from "../../compile/codegen" import {alwaysValidSchema, mergeEvaluated, checkStrictMode} from "../../compile/util" import {validateArray} from "../code" const def: CodeKeywordDefinition = { keyword: "items", type: "array", schemaType: ["object", "array", "boolean"], before: "uniqueItems", code(cxt: KeywordCxt) { const {schema, it} = cxt if (Array.isArray(schema)) return validateTuple(cxt, "additionalItems", schema) it.items = true if (alwaysValidSchema(it, schema)) return cxt.ok(validateArray(cxt)) }, } export function validateTuple( cxt: KeywordCxt, extraItems: string, schArr: AnySchema[] = cxt.schema ): void { const {gen, parentSchema, data, keyword, it} = cxt checkStrictTuple(parentSchema) if (it.opts.unevaluated && schArr.length && it.items !== true) { it.items = mergeEvaluated.items(gen, schArr.length, it.items) } const valid = gen.name("valid") const len = gen.const("len", _`${data}.length`) schArr.forEach((sch: AnySchema, i: number) => { if (alwaysValidSchema(it, sch)) return gen.if(_`${len} > ${i}`, () => cxt.subschema( { keyword, schemaProp: i, dataProp: i, }, valid ) ) cxt.ok(valid) }) function checkStrictTuple(sch: AnySchemaObject): void { const {opts, errSchemaPath} = it const l = schArr.length const fullTuple = l === sch.minItems && (l === sch.maxItems || sch[extraItems] === false) if (opts.strictTuples && !fullTuple) { const msg = `"${keyword}" is ${l}-tuple, but minItems or maxItems/${extraItems} are not specified or different at path "${errSchemaPath}"` checkStrictMode(it, msg, opts.strictTuples) } } } export default def ================================================ FILE: lib/vocabularies/applicator/items2020.ts ================================================ import type { CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str} from "../../compile/codegen" import {alwaysValidSchema} from "../../compile/util" import {validateArray} from "../code" import {validateAdditionalItems} from "./additionalItems" export type ItemsError = ErrorObject<"items", {limit: number}, AnySchema> const error: KeywordErrorDefinition = { message: ({params: {len}}) => str`must NOT have more than ${len} items`, params: ({params: {len}}) => _`{limit: ${len}}`, } const def: CodeKeywordDefinition = { keyword: "items", type: "array", schemaType: ["object", "boolean"], before: "uniqueItems", error, code(cxt: KeywordCxt) { const {schema, parentSchema, it} = cxt const {prefixItems} = parentSchema it.items = true if (alwaysValidSchema(it, schema)) return if (prefixItems) validateAdditionalItems(cxt, prefixItems) else cxt.ok(validateArray(cxt)) }, } export default def ================================================ FILE: lib/vocabularies/applicator/not.ts ================================================ import type {CodeKeywordDefinition, ErrorNoParams, AnySchema} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {alwaysValidSchema} from "../../compile/util" export type NotKeywordError = ErrorNoParams<"not", AnySchema> const def: CodeKeywordDefinition = { keyword: "not", schemaType: ["object", "boolean"], trackErrors: true, code(cxt: KeywordCxt) { const {gen, schema, it} = cxt if (alwaysValidSchema(it, schema)) { cxt.fail() return } const valid = gen.name("valid") cxt.subschema( { keyword: "not", compositeRule: true, createErrors: false, allErrors: false, }, valid ) cxt.failResult( valid, () => cxt.reset(), () => cxt.error() ) }, error: {message: "must NOT be valid"}, } export default def ================================================ FILE: lib/vocabularies/applicator/oneOf.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, Name} from "../../compile/codegen" import {alwaysValidSchema} from "../../compile/util" import {SchemaCxt} from "../../compile" export type OneOfError = ErrorObject< "oneOf", {passingSchemas: [number, number] | null}, AnySchema[] > const error: KeywordErrorDefinition = { message: "must match exactly one schema in oneOf", params: ({params}) => _`{passingSchemas: ${params.passing}}`, } const def: CodeKeywordDefinition = { keyword: "oneOf", schemaType: "array", trackErrors: true, error, code(cxt: KeywordCxt) { const {gen, schema, parentSchema, it} = cxt /* istanbul ignore if */ if (!Array.isArray(schema)) throw new Error("ajv implementation error") if (it.opts.discriminator && parentSchema.discriminator) return const schArr: AnySchema[] = schema const valid = gen.let("valid", false) const passing = gen.let("passing", null) const schValid = gen.name("_valid") cxt.setParams({passing}) // TODO possibly fail straight away (with warning or exception) if there are two empty always valid schemas gen.block(validateOneOf) cxt.result( valid, () => cxt.reset(), () => cxt.error(true) ) function validateOneOf(): void { schArr.forEach((sch: AnySchema, i: number) => { let schCxt: SchemaCxt | undefined if (alwaysValidSchema(it, sch)) { gen.var(schValid, true) } else { schCxt = cxt.subschema( { keyword: "oneOf", schemaProp: i, compositeRule: true, }, schValid ) } if (i > 0) { gen .if(_`${schValid} && ${valid}`) .assign(valid, false) .assign(passing, _`[${passing}, ${i}]`) .else() } gen.if(schValid, () => { gen.assign(valid, true) gen.assign(passing, i) if (schCxt) cxt.mergeEvaluated(schCxt, Name) }) }) } }, } export default def ================================================ FILE: lib/vocabularies/applicator/patternProperties.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {allSchemaProperties, usePattern} from "../code" import {_, not, Name} from "../../compile/codegen" import {alwaysValidSchema, checkStrictMode} from "../../compile/util" import {evaluatedPropsToName, Type} from "../../compile/util" import {AnySchema} from "../../types" const def: CodeKeywordDefinition = { keyword: "patternProperties", type: "object", schemaType: "object", code(cxt: KeywordCxt) { const {gen, schema, data, parentSchema, it} = cxt const {opts} = it const patterns = allSchemaProperties(schema) const alwaysValidPatterns = patterns.filter((p) => alwaysValidSchema(it, schema[p] as AnySchema) ) if ( patterns.length === 0 || (alwaysValidPatterns.length === patterns.length && (!it.opts.unevaluated || it.props === true)) ) { return } const checkProperties = opts.strictSchema && !opts.allowMatchingProperties && parentSchema.properties const valid = gen.name("valid") if (it.props !== true && !(it.props instanceof Name)) { it.props = evaluatedPropsToName(gen, it.props) } const {props} = it validatePatternProperties() function validatePatternProperties(): void { for (const pat of patterns) { if (checkProperties) checkMatchingProperties(pat) if (it.allErrors) { validateProperties(pat) } else { gen.var(valid, true) // TODO var validateProperties(pat) gen.if(valid) } } } function checkMatchingProperties(pat: string): void { for (const prop in checkProperties) { if (new RegExp(pat).test(prop)) { checkStrictMode( it, `property ${prop} matches pattern ${pat} (use allowMatchingProperties)` ) } } } function validateProperties(pat: string): void { gen.forIn("key", data, (key) => { gen.if(_`${usePattern(cxt, pat)}.test(${key})`, () => { const alwaysValid = alwaysValidPatterns.includes(pat) if (!alwaysValid) { cxt.subschema( { keyword: "patternProperties", schemaProp: pat, dataProp: key, dataPropType: Type.Str, }, valid ) } if (it.opts.unevaluated && props !== true) { gen.assign(_`${props}[${key}]`, true) } else if (!alwaysValid && !it.allErrors) { // can short-circuit if `unevaluatedProperties` is not supported (opts.next === false) // or if all properties were evaluated (props === true) gen.if(not(valid), () => gen.break()) } }) }) } }, } export default def ================================================ FILE: lib/vocabularies/applicator/prefixItems.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import {validateTuple} from "./items" const def: CodeKeywordDefinition = { keyword: "prefixItems", type: "array", schemaType: ["array"], before: "uniqueItems", code: (cxt) => validateTuple(cxt, "items"), } export default def ================================================ FILE: lib/vocabularies/applicator/properties.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import {KeywordCxt} from "../../compile/validate" import {propertyInData, allSchemaProperties} from "../code" import {alwaysValidSchema, toHash, mergeEvaluated} from "../../compile/util" import apDef from "./additionalProperties" const def: CodeKeywordDefinition = { keyword: "properties", type: "object", schemaType: "object", code(cxt: KeywordCxt) { const {gen, schema, parentSchema, data, it} = cxt if (it.opts.removeAdditional === "all" && parentSchema.additionalProperties === undefined) { apDef.code(new KeywordCxt(it, apDef, "additionalProperties")) } const allProps = allSchemaProperties(schema) for (const prop of allProps) { it.definedProperties.add(prop) } if (it.opts.unevaluated && allProps.length && it.props !== true) { it.props = mergeEvaluated.props(gen, toHash(allProps), it.props) } const properties = allProps.filter((p) => !alwaysValidSchema(it, schema[p])) if (properties.length === 0) return const valid = gen.name("valid") for (const prop of properties) { if (hasDefault(prop)) { applyPropertySchema(prop) } else { gen.if(propertyInData(gen, data, prop, it.opts.ownProperties)) applyPropertySchema(prop) if (!it.allErrors) gen.else().var(valid, true) gen.endIf() } cxt.it.definedProperties.add(prop) cxt.ok(valid) } function hasDefault(prop: string): boolean | undefined { return it.opts.useDefaults && !it.compositeRule && schema[prop].default !== undefined } function applyPropertySchema(prop: string): void { cxt.subschema( { keyword: "properties", schemaProp: prop, dataProp: prop, }, valid ) } }, } export default def ================================================ FILE: lib/vocabularies/applicator/propertyNames.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, not} from "../../compile/codegen" import {alwaysValidSchema} from "../../compile/util" export type PropertyNamesError = ErrorObject<"propertyNames", {propertyName: string}, AnySchema> const error: KeywordErrorDefinition = { message: "property name must be valid", params: ({params}) => _`{propertyName: ${params.propertyName}}`, } const def: CodeKeywordDefinition = { keyword: "propertyNames", type: "object", schemaType: ["object", "boolean"], error, code(cxt: KeywordCxt) { const {gen, schema, data, it} = cxt if (alwaysValidSchema(it, schema)) return const valid = gen.name("valid") gen.forIn("key", data, (key) => { cxt.setParams({propertyName: key}) cxt.subschema( { keyword: "propertyNames", data: key, dataTypes: ["string"], propertyName: key, compositeRule: true, }, valid ) gen.if(not(valid), () => { cxt.error(true) if (!it.allErrors) gen.break() }) }) cxt.ok(valid) }, } export default def ================================================ FILE: lib/vocabularies/applicator/thenElse.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {checkStrictMode} from "../../compile/util" const def: CodeKeywordDefinition = { keyword: ["then", "else"], schemaType: ["object", "boolean"], code({keyword, parentSchema, it}: KeywordCxt) { if (parentSchema.if === undefined) checkStrictMode(it, `"${keyword}" without "if" is ignored`) }, } export default def ================================================ FILE: lib/vocabularies/code.ts ================================================ import type {AnySchema, SchemaMap} from "../types" import type {SchemaCxt} from "../compile" import type {KeywordCxt} from "../compile/validate" import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen" import {alwaysValidSchema, Type} from "../compile/util" import N from "../compile/names" import {useFunc} from "../compile/util" export function checkReportMissingProp(cxt: KeywordCxt, prop: string): void { const {gen, data, it} = cxt gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => { cxt.setParams({missingProperty: _`${prop}`}, true) cxt.error() }) } export function checkMissingProp( {gen, data, it: {opts}}: KeywordCxt, properties: string[], missing: Name ): Code { return or( ...properties.map((prop) => and(noPropertyInData(gen, data, prop, opts.ownProperties), _`${missing} = ${prop}`) ) ) } export function reportMissingProp(cxt: KeywordCxt, missing: Name): void { cxt.setParams({missingProperty: missing}, true) cxt.error() } export function hasPropFunc(gen: CodeGen): Name { return gen.scopeValue("func", { // eslint-disable-next-line @typescript-eslint/unbound-method ref: Object.prototype.hasOwnProperty, code: _`Object.prototype.hasOwnProperty`, }) } export function isOwnProperty(gen: CodeGen, data: Name, property: Name | string): Code { return _`${hasPropFunc(gen)}.call(${data}, ${property})` } export function propertyInData( gen: CodeGen, data: Name, property: Name | string, ownProperties?: boolean ): Code { const cond = _`${data}${getProperty(property)} !== undefined` return ownProperties ? _`${cond} && ${isOwnProperty(gen, data, property)}` : cond } export function noPropertyInData( gen: CodeGen, data: Name, property: Name | string, ownProperties?: boolean ): Code { const cond = _`${data}${getProperty(property)} === undefined` return ownProperties ? or(cond, not(isOwnProperty(gen, data, property))) : cond } export function allSchemaProperties(schemaMap?: SchemaMap): string[] { return schemaMap ? Object.keys(schemaMap).filter((p) => p !== "__proto__") : [] } export function schemaProperties(it: SchemaCxt, schemaMap: SchemaMap): string[] { return allSchemaProperties(schemaMap).filter( (p) => !alwaysValidSchema(it, schemaMap[p] as AnySchema) ) } export function callValidateCode( {schemaCode, data, it: {gen, topSchemaRef, schemaPath, errorPath}, it}: KeywordCxt, func: Code, context: Code, passSchema?: boolean ): Code { const dataAndSchema = passSchema ? _`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data const valCxt: [Name, Code | number][] = [ [N.instancePath, strConcat(N.instancePath, errorPath)], [N.parentData, it.parentData], [N.parentDataProperty, it.parentDataProperty], [N.rootData, N.rootData], ] if (it.opts.dynamicRef) valCxt.push([N.dynamicAnchors, N.dynamicAnchors]) const args = _`${dataAndSchema}, ${gen.object(...valCxt)}` return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})` } const newRegExp = _`new RegExp` export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name { const u = opts.unicodeRegExp ? "u" : "" const {regExp} = opts.code const rx = regExp(pattern, u) return gen.scopeValue("pattern", { key: rx.toString(), ref: rx, code: _`${regExp.code === "new RegExp" ? newRegExp : useFunc(gen, regExp)}(${pattern}, ${u})`, }) } export function validateArray(cxt: KeywordCxt): Name { const {gen, data, keyword, it} = cxt const valid = gen.name("valid") if (it.allErrors) { const validArr = gen.let("valid", true) validateItems(() => gen.assign(validArr, false)) return validArr } gen.var(valid, true) validateItems(() => gen.break()) return valid function validateItems(notValid: () => void): void { const len = gen.const("len", _`${data}.length`) gen.forRange("i", 0, len, (i) => { cxt.subschema( { keyword, dataProp: i, dataPropType: Type.Num, }, valid ) gen.if(not(valid), notValid) }) } } export function validateUnion(cxt: KeywordCxt): void { const {gen, schema, keyword, it} = cxt /* istanbul ignore if */ if (!Array.isArray(schema)) throw new Error("ajv implementation error") const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch)) if (alwaysValid && !it.opts.unevaluated) return const valid = gen.let("valid", false) const schValid = gen.name("_valid") gen.block(() => schema.forEach((_sch: AnySchema, i: number) => { const schCxt = cxt.subschema( { keyword, schemaProp: i, compositeRule: true, }, schValid ) gen.assign(valid, _`${valid} || ${schValid}`) const merged = cxt.mergeValidEvaluated(schCxt, schValid) // can short-circuit if `unevaluatedProperties/Items` not supported (opts.unevaluated !== true) // or if all properties and items were evaluated (it.props === true && it.items === true) if (!merged) gen.if(not(valid)) }) ) cxt.result( valid, () => cxt.reset(), () => cxt.error(true) ) } ================================================ FILE: lib/vocabularies/core/id.ts ================================================ import type {CodeKeywordDefinition} from "../../types" const def: CodeKeywordDefinition = { keyword: "id", code() { throw new Error('NOT SUPPORTED: keyword "id", use "$id" for schema ID') }, } export default def ================================================ FILE: lib/vocabularies/core/index.ts ================================================ import type {Vocabulary} from "../../types" import idKeyword from "./id" import refKeyword from "./ref" const core: Vocabulary = [ "$schema", "$id", "$defs", "$vocabulary", {keyword: "$comment"}, "definitions", idKeyword, refKeyword, ] export default core ================================================ FILE: lib/vocabularies/core/ref.ts ================================================ import type {CodeKeywordDefinition, AnySchema} from "../../types" import type {KeywordCxt} from "../../compile/validate" import MissingRefError from "../../compile/ref_error" import {callValidateCode} from "../code" import {_, nil, stringify, Code, Name} from "../../compile/codegen" import N from "../../compile/names" import {SchemaEnv, resolveRef} from "../../compile" import {mergeEvaluated} from "../../compile/util" const def: CodeKeywordDefinition = { keyword: "$ref", schemaType: "string", code(cxt: KeywordCxt): void { const {gen, schema: $ref, it} = cxt const {baseId, schemaEnv: env, validateName, opts, self} = it const {root} = env if (($ref === "#" || $ref === "#/") && baseId === root.baseId) return callRootRef() const schOrEnv = resolveRef.call(self, root, baseId, $ref) if (schOrEnv === undefined) throw new MissingRefError(it.opts.uriResolver, baseId, $ref) if (schOrEnv instanceof SchemaEnv) return callValidate(schOrEnv) return inlineRefSchema(schOrEnv) function callRootRef(): void { if (env === root) return callRef(cxt, validateName, env, env.$async) const rootName = gen.scopeValue("root", {ref: root}) return callRef(cxt, _`${rootName}.validate`, root, root.$async) } function callValidate(sch: SchemaEnv): void { const v = getValidate(cxt, sch) callRef(cxt, v, sch, sch.$async) } function inlineRefSchema(sch: AnySchema): void { const schName = gen.scopeValue( "schema", opts.code.source === true ? {ref: sch, code: stringify(sch)} : {ref: sch} ) const valid = gen.name("valid") const schCxt = cxt.subschema( { schema: sch, dataTypes: [], schemaPath: nil, topSchemaRef: schName, errSchemaPath: $ref, }, valid ) cxt.mergeEvaluated(schCxt) cxt.ok(valid) } }, } export function getValidate(cxt: KeywordCxt, sch: SchemaEnv): Code { const {gen} = cxt return sch.validate ? gen.scopeValue("validate", {ref: sch.validate}) : _`${gen.scopeValue("wrapper", {ref: sch})}.validate` } export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void { const {gen, it} = cxt const {allErrors, schemaEnv: env, opts} = it const passCxt = opts.passContext ? N.this : nil if ($async) callAsyncRef() else callSyncRef() function callAsyncRef(): void { if (!env.$async) throw new Error("async schema referenced by sync schema") const valid = gen.let("valid") gen.try( () => { gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`) addEvaluatedFrom(v) // TODO will not work with async, it has to be returned with the result if (!allErrors) gen.assign(valid, true) }, (e) => { gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e)) addErrorsFrom(e) if (!allErrors) gen.assign(valid, false) } ) cxt.ok(valid) } function callSyncRef(): void { cxt.result( callValidateCode(cxt, v, passCxt), () => addEvaluatedFrom(v), () => addErrorsFrom(v) ) } function addErrorsFrom(source: Code): void { const errs = _`${source}.errors` gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged gen.assign(N.errors, _`${N.vErrors}.length`) } function addEvaluatedFrom(source: Code): void { if (!it.opts.unevaluated) return const schEvaluated = sch?.validate?.evaluated // TODO refactor if (it.props !== true) { if (schEvaluated && !schEvaluated.dynamicProps) { if (schEvaluated.props !== undefined) { it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props) } } else { const props = gen.var("props", _`${source}.evaluated.props`) it.props = mergeEvaluated.props(gen, props, it.props, Name) } } if (it.items !== true) { if (schEvaluated && !schEvaluated.dynamicItems) { if (schEvaluated.items !== undefined) { it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items) } } else { const items = gen.var("items", _`${source}.evaluated.items`) it.items = mergeEvaluated.items(gen, items, it.items, Name) } } } } export default def ================================================ FILE: lib/vocabularies/discriminator/index.ts ================================================ import type {CodeKeywordDefinition, AnySchemaObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, getProperty, Name} from "../../compile/codegen" import {DiscrError, DiscrErrorObj} from "../discriminator/types" import {resolveRef, SchemaEnv} from "../../compile" import MissingRefError from "../../compile/ref_error" import {schemaHasRulesButRef} from "../../compile/util" export type DiscriminatorError = DiscrErrorObj | DiscrErrorObj const error: KeywordErrorDefinition = { message: ({params: {discrError, tagName}}) => discrError === DiscrError.Tag ? `tag "${tagName}" must be string` : `value of tag "${tagName}" must be in oneOf`, params: ({params: {discrError, tag, tagName}}) => _`{error: ${discrError}, tag: ${tagName}, tagValue: ${tag}}`, } const def: CodeKeywordDefinition = { keyword: "discriminator", type: "object", schemaType: "object", error, code(cxt: KeywordCxt) { const {gen, data, schema, parentSchema, it} = cxt const {oneOf} = parentSchema if (!it.opts.discriminator) { throw new Error("discriminator: requires discriminator option") } const tagName = schema.propertyName if (typeof tagName != "string") throw new Error("discriminator: requires propertyName") if (schema.mapping) throw new Error("discriminator: mapping is not supported") if (!oneOf) throw new Error("discriminator: requires oneOf keyword") const valid = gen.let("valid", false) const tag = gen.const("tag", _`${data}${getProperty(tagName)}`) gen.if( _`typeof ${tag} == "string"`, () => validateMapping(), () => cxt.error(false, {discrError: DiscrError.Tag, tag, tagName}) ) cxt.ok(valid) function validateMapping(): void { const mapping = getMapping() gen.if(false) for (const tagValue in mapping) { gen.elseIf(_`${tag} === ${tagValue}`) gen.assign(valid, applyTagSchema(mapping[tagValue])) } gen.else() cxt.error(false, {discrError: DiscrError.Mapping, tag, tagName}) gen.endIf() } function applyTagSchema(schemaProp?: number): Name { const _valid = gen.name("valid") const schCxt = cxt.subschema({keyword: "oneOf", schemaProp}, _valid) cxt.mergeEvaluated(schCxt, Name) return _valid } function getMapping(): {[T in string]?: number} { const oneOfMapping: {[T in string]?: number} = {} const topRequired = hasRequired(parentSchema) let tagRequired = true for (let i = 0; i < oneOf.length; i++) { let sch = oneOf[i] if (sch?.$ref && !schemaHasRulesButRef(sch, it.self.RULES)) { const ref = sch.$ref sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref) if (sch instanceof SchemaEnv) sch = sch.schema if (sch === undefined) throw new MissingRefError(it.opts.uriResolver, it.baseId, ref) } const propSch = sch?.properties?.[tagName] if (typeof propSch != "object") { throw new Error( `discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"` ) } tagRequired = tagRequired && (topRequired || hasRequired(sch)) addMappings(propSch, i) } if (!tagRequired) throw new Error(`discriminator: "${tagName}" must be required`) return oneOfMapping function hasRequired({required}: AnySchemaObject): boolean { return Array.isArray(required) && required.includes(tagName) } function addMappings(sch: AnySchemaObject, i: number): void { if (sch.const) { addMapping(sch.const, i) } else if (sch.enum) { for (const tagValue of sch.enum) { addMapping(tagValue, i) } } else { throw new Error(`discriminator: "properties/${tagName}" must have "const" or "enum"`) } } function addMapping(tagValue: unknown, i: number): void { if (typeof tagValue != "string" || tagValue in oneOfMapping) { throw new Error(`discriminator: "${tagName}" values must be unique strings`) } oneOfMapping[tagValue] = i } } }, } export default def ================================================ FILE: lib/vocabularies/discriminator/types.ts ================================================ import type {ErrorObject} from "../../types" export enum DiscrError { Tag = "tag", Mapping = "mapping", } export type DiscrErrorObj = ErrorObject< "discriminator", {error: E; tag: string; tagValue: unknown}, string > ================================================ FILE: lib/vocabularies/draft2020.ts ================================================ import type {Vocabulary} from "../types" import coreVocabulary from "./core" import validationVocabulary from "./validation" import getApplicatorVocabulary from "./applicator" import dynamicVocabulary from "./dynamic" import nextVocabulary from "./next" import unevaluatedVocabulary from "./unevaluated" import formatVocabulary from "./format" import {metadataVocabulary, contentVocabulary} from "./metadata" const draft2020Vocabularies: Vocabulary[] = [ dynamicVocabulary, coreVocabulary, validationVocabulary, getApplicatorVocabulary(true), formatVocabulary, metadataVocabulary, contentVocabulary, nextVocabulary, unevaluatedVocabulary, ] export default draft2020Vocabularies ================================================ FILE: lib/vocabularies/draft7.ts ================================================ import type {Vocabulary} from "../types" import coreVocabulary from "./core" import validationVocabulary from "./validation" import getApplicatorVocabulary from "./applicator" import formatVocabulary from "./format" import {metadataVocabulary, contentVocabulary} from "./metadata" const draft7Vocabularies: Vocabulary[] = [ coreVocabulary, validationVocabulary, getApplicatorVocabulary(), formatVocabulary, metadataVocabulary, contentVocabulary, ] export default draft7Vocabularies ================================================ FILE: lib/vocabularies/dynamic/dynamicAnchor.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, getProperty, Code} from "../../compile/codegen" import N from "../../compile/names" import {SchemaEnv, compileSchema} from "../../compile" import {getValidate} from "../core/ref" const def: CodeKeywordDefinition = { keyword: "$dynamicAnchor", schemaType: "string", code: (cxt) => dynamicAnchor(cxt, cxt.schema), } export function dynamicAnchor(cxt: KeywordCxt, anchor: string): void { const {gen, it} = cxt it.schemaEnv.root.dynamicAnchors[anchor] = true const v = _`${N.dynamicAnchors}${getProperty(anchor)}` const validate = it.errSchemaPath === "#" ? it.validateName : _getValidate(cxt) gen.if(_`!${v}`, () => gen.assign(v, validate)) } function _getValidate(cxt: KeywordCxt): Code { const {schemaEnv, schema, self} = cxt.it const {root, baseId, localRefs, meta} = schemaEnv.root const {schemaId} = self.opts const sch = new SchemaEnv({schema, schemaId, root, baseId, localRefs, meta}) compileSchema.call(self, sch) return getValidate(cxt, sch) } export default def ================================================ FILE: lib/vocabularies/dynamic/dynamicRef.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, getProperty, Code, Name} from "../../compile/codegen" import N from "../../compile/names" import {callRef} from "../core/ref" const def: CodeKeywordDefinition = { keyword: "$dynamicRef", schemaType: "string", code: (cxt) => dynamicRef(cxt, cxt.schema), } export function dynamicRef(cxt: KeywordCxt, ref: string): void { const {gen, keyword, it} = cxt if (ref[0] !== "#") throw new Error(`"${keyword}" only supports hash fragment reference`) const anchor = ref.slice(1) if (it.allErrors) { _dynamicRef() } else { const valid = gen.let("valid", false) _dynamicRef(valid) cxt.ok(valid) } function _dynamicRef(valid?: Name): void { // TODO the assumption here is that `recursiveRef: #` always points to the root // of the schema object, which is not correct, because there may be $id that // makes # point to it, and the target schema may not contain dynamic/recursiveAnchor. // Because of that 2 tests in recursiveRef.json fail. // This is a similar problem to #815 (`$id` doesn't alter resolution scope for `{ "$ref": "#" }`). // (This problem is not tested in JSON-Schema-Test-Suite) if (it.schemaEnv.root.dynamicAnchors[anchor]) { const v = gen.let("_v", _`${N.dynamicAnchors}${getProperty(anchor)}`) gen.if(v, _callRef(v, valid), _callRef(it.validateName, valid)) } else { _callRef(it.validateName, valid)() } } function _callRef(validate: Code, valid?: Name): () => void { return valid ? () => gen.block(() => { callRef(cxt, validate) gen.let(valid, true) }) : () => callRef(cxt, validate) } } export default def ================================================ FILE: lib/vocabularies/dynamic/index.ts ================================================ import type {Vocabulary} from "../../types" import dynamicAnchor from "./dynamicAnchor" import dynamicRef from "./dynamicRef" import recursiveAnchor from "./recursiveAnchor" import recursiveRef from "./recursiveRef" const dynamic: Vocabulary = [dynamicAnchor, dynamicRef, recursiveAnchor, recursiveRef] export default dynamic ================================================ FILE: lib/vocabularies/dynamic/recursiveAnchor.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import {dynamicAnchor} from "./dynamicAnchor" import {checkStrictMode} from "../../compile/util" const def: CodeKeywordDefinition = { keyword: "$recursiveAnchor", schemaType: "boolean", code(cxt) { if (cxt.schema) dynamicAnchor(cxt, "") else checkStrictMode(cxt.it, "$recursiveAnchor: false is ignored") }, } export default def ================================================ FILE: lib/vocabularies/dynamic/recursiveRef.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import {dynamicRef} from "./dynamicRef" const def: CodeKeywordDefinition = { keyword: "$recursiveRef", schemaType: "string", code: (cxt) => dynamicRef(cxt, cxt.schema), } export default def ================================================ FILE: lib/vocabularies/errors.ts ================================================ import type {TypeError} from "../compile/validate/dataType" import type {ApplicatorKeywordError} from "./applicator" import type {ValidationKeywordError} from "./validation" import type {FormatError} from "./format/format" import type {UnevaluatedPropertiesError} from "./unevaluated/unevaluatedProperties" import type {UnevaluatedItemsError} from "./unevaluated/unevaluatedItems" import type {DependentRequiredError} from "./validation/dependentRequired" import type {DiscriminatorError} from "./discriminator" export type DefinedError = | TypeError | ApplicatorKeywordError | ValidationKeywordError | FormatError | UnevaluatedPropertiesError | UnevaluatedItemsError | DependentRequiredError | DiscriminatorError ================================================ FILE: lib/vocabularies/format/format.ts ================================================ import type { AddedFormat, FormatValidator, AsyncFormatValidator, CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, nil, or, Code, getProperty, regexpCode} from "../../compile/codegen" type FormatValidate = | FormatValidator | FormatValidator | AsyncFormatValidator | AsyncFormatValidator | RegExp | string | true export type FormatError = ErrorObject<"format", {format: string}, string | {$data: string}> const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`must match format "${schemaCode}"`, params: ({schemaCode}) => _`{format: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: "format", type: ["number", "string"], schemaType: "string", $data: true, error, code(cxt: KeywordCxt, ruleType?: string) { const {gen, data, $data, schema, schemaCode, it} = cxt const {opts, errSchemaPath, schemaEnv, self} = it if (!opts.validateFormats) return if ($data) validate$DataFormat() else validateFormat() function validate$DataFormat(): void { const fmts = gen.scopeValue("formats", { ref: self.formats, code: opts.code.formats, }) const fDef = gen.const("fDef", _`${fmts}[${schemaCode}]`) const fType = gen.let("fType") const format = gen.let("format") // TODO simplify gen.if( _`typeof ${fDef} == "object" && !(${fDef} instanceof RegExp)`, () => gen.assign(fType, _`${fDef}.type || "string"`).assign(format, _`${fDef}.validate`), () => gen.assign(fType, _`"string"`).assign(format, fDef) ) cxt.fail$data(or(unknownFmt(), invalidFmt())) function unknownFmt(): Code { if (opts.strictSchema === false) return nil return _`${schemaCode} && !${format}` } function invalidFmt(): Code { const callFormat = schemaEnv.$async ? _`(${fDef}.async ? await ${format}(${data}) : ${format}(${data}))` : _`${format}(${data})` const validData = _`(typeof ${format} == "function" ? ${callFormat} : ${format}.test(${data}))` return _`${format} && ${format} !== true && ${fType} === ${ruleType} && !${validData}` } } function validateFormat(): void { const formatDef: AddedFormat | undefined = self.formats[schema] if (!formatDef) { unknownFormat() return } if (formatDef === true) return const [fmtType, format, fmtRef] = getFormat(formatDef) if (fmtType === ruleType) cxt.pass(validCondition()) function unknownFormat(): void { if (opts.strictSchema === false) { self.logger.warn(unknownMsg()) return } throw new Error(unknownMsg()) function unknownMsg(): string { return `unknown format "${schema as string}" ignored in schema at path "${errSchemaPath}"` } } function getFormat(fmtDef: AddedFormat): [string, FormatValidate, Code] { const code = fmtDef instanceof RegExp ? regexpCode(fmtDef) : opts.code.formats ? _`${opts.code.formats}${getProperty(schema)}` : undefined const fmt = gen.scopeValue("formats", {key: schema, ref: fmtDef, code}) if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) { return [fmtDef.type || "string", fmtDef.validate, _`${fmt}.validate`] } return ["string", fmtDef, fmt] } function validCondition(): Code { if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) { if (!schemaEnv.$async) throw new Error("async format in sync schema") return _`await ${fmtRef}(${data})` } return typeof format == "function" ? _`${fmtRef}(${data})` : _`${fmtRef}.test(${data})` } } }, } export default def ================================================ FILE: lib/vocabularies/format/index.ts ================================================ import type {Vocabulary} from "../../types" import formatKeyword from "./format" const format: Vocabulary = [formatKeyword] export default format ================================================ FILE: lib/vocabularies/jtd/discriminator.ts ================================================ import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, not, getProperty, Name} from "../../compile/codegen" import {checkMetadata} from "./metadata" import {checkNullableObject} from "./nullable" import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error" import {DiscrError, DiscrErrorObj} from "../discriminator/types" export type JTDDiscriminatorError = | _JTDTypeError<"discriminator", "object", string> | DiscrErrorObj | DiscrErrorObj const error: KeywordErrorDefinition = { message: (cxt) => { const {schema, params} = cxt return params.discrError ? params.discrError === DiscrError.Tag ? `tag "${schema}" must be string` : `value of tag "${schema}" must be in mapping` : typeErrorMessage(cxt, "object") }, params: (cxt) => { const {schema, params} = cxt return params.discrError ? _`{error: ${params.discrError}, tag: ${schema}, tagValue: ${params.tag}}` : typeErrorParams(cxt, "object") }, } const def: CodeKeywordDefinition = { keyword: "discriminator", schemaType: "string", implements: ["mapping"], error, code(cxt: KeywordCxt) { checkMetadata(cxt) const {gen, data, schema, parentSchema} = cxt const [valid, cond] = checkNullableObject(cxt, data) gen.if(cond) validateDiscriminator() gen.elseIf(not(valid)) cxt.error() gen.endIf() cxt.ok(valid) function validateDiscriminator(): void { const tag = gen.const("tag", _`${data}${getProperty(schema)}`) gen.if(_`${tag} === undefined`) cxt.error(false, {discrError: DiscrError.Tag, tag}) gen.elseIf(_`typeof ${tag} == "string"`) validateMapping(tag) gen.else() cxt.error(false, {discrError: DiscrError.Tag, tag}, {instancePath: schema}) gen.endIf() } function validateMapping(tag: Name): void { gen.if(false) for (const tagValue in parentSchema.mapping) { gen.elseIf(_`${tag} === ${tagValue}`) gen.assign(valid, applyTagSchema(tagValue)) } gen.else() cxt.error( false, {discrError: DiscrError.Mapping, tag}, {instancePath: schema, schemaPath: "mapping", parentSchema: true} ) gen.endIf() } function applyTagSchema(schemaProp: string): Name { const _valid = gen.name("valid") cxt.subschema( { keyword: "mapping", schemaProp, jtdDiscriminator: schema, }, _valid ) return _valid } }, } export default def ================================================ FILE: lib/vocabularies/jtd/elements.ts ================================================ import type {CodeKeywordDefinition, SchemaObject} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {alwaysValidSchema} from "../../compile/util" import {validateArray} from "../code" import {_, not} from "../../compile/codegen" import {checkMetadata} from "./metadata" import {checkNullable} from "./nullable" import {typeError, _JTDTypeError} from "./error" export type JTDElementsError = _JTDTypeError<"elements", "array", SchemaObject> const def: CodeKeywordDefinition = { keyword: "elements", schemaType: "object", error: typeError("array"), code(cxt: KeywordCxt) { checkMetadata(cxt) const {gen, data, schema, it} = cxt if (alwaysValidSchema(it, schema)) return const [valid] = checkNullable(cxt) gen.if(not(valid), () => gen.if( _`Array.isArray(${data})`, () => gen.assign(valid, validateArray(cxt)), () => cxt.error() ) ) cxt.ok(valid) }, } export default def ================================================ FILE: lib/vocabularies/jtd/enum.ts ================================================ import type {CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, or, and, Code} from "../../compile/codegen" import {checkMetadata} from "./metadata" import {checkNullable} from "./nullable" export type JTDEnumError = ErrorObject<"enum", {allowedValues: string[]}, string[]> const error: KeywordErrorDefinition = { message: "must be equal to one of the allowed values", params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: "enum", schemaType: "array", error, code(cxt: KeywordCxt) { checkMetadata(cxt) const {gen, data, schema, schemaValue, parentSchema, it} = cxt if (schema.length === 0) throw new Error("enum must have non-empty array") if (schema.length !== new Set(schema).size) throw new Error("enum items must be unique") let valid: Code const isString = _`typeof ${data} == "string"` if (schema.length >= it.opts.loopEnum) { let cond: Code ;[valid, cond] = checkNullable(cxt, isString) gen.if(cond, loopEnum) } else { /* istanbul ignore if */ if (!Array.isArray(schema)) throw new Error("ajv implementation error") valid = and(isString, or(...schema.map((value: string) => _`${data} === ${value}`))) if (parentSchema.nullable) valid = or(_`${data} === null`, valid) } cxt.pass(valid) function loopEnum(): void { gen.forOf("v", schemaValue as Code, (v) => gen.if(_`${valid} = ${data} === ${v}`, () => gen.break()) ) } }, } export default def ================================================ FILE: lib/vocabularies/jtd/error.ts ================================================ import type {KeywordErrorDefinition, KeywordErrorCxt, ErrorObject} from "../../types" import {_, Code} from "../../compile/codegen" export type _JTDTypeError = ErrorObject< K, {type: T; nullable: boolean}, S > export function typeError(t: string): KeywordErrorDefinition { return { message: (cxt) => typeErrorMessage(cxt, t), params: (cxt) => typeErrorParams(cxt, t), } } export function typeErrorMessage({parentSchema}: KeywordErrorCxt, t: string): string { return parentSchema?.nullable ? `must be ${t} or null` : `must be ${t}` } export function typeErrorParams({parentSchema}: KeywordErrorCxt, t: string): Code { return _`{type: ${t}, nullable: ${!!parentSchema?.nullable}}` } ================================================ FILE: lib/vocabularies/jtd/index.ts ================================================ import type {Vocabulary} from "../../types" import refKeyword from "./ref" import typeKeyword, {JTDTypeError} from "./type" import enumKeyword, {JTDEnumError} from "./enum" import elements, {JTDElementsError} from "./elements" import properties, {JTDPropertiesError} from "./properties" import optionalProperties from "./optionalProperties" import discriminator, {JTDDiscriminatorError} from "./discriminator" import values, {JTDValuesError} from "./values" import union from "./union" import metadata from "./metadata" const jtdVocabulary: Vocabulary = [ "definitions", refKeyword, typeKeyword, enumKeyword, elements, properties, optionalProperties, discriminator, values, union, metadata, {keyword: "additionalProperties", schemaType: "boolean"}, {keyword: "nullable", schemaType: "boolean"}, ] export default jtdVocabulary export type JTDErrorObject = | JTDTypeError | JTDEnumError | JTDElementsError | JTDPropertiesError | JTDDiscriminatorError | JTDValuesError ================================================ FILE: lib/vocabularies/jtd/metadata.ts ================================================ import {KeywordCxt} from "../../ajv" import type {CodeKeywordDefinition} from "../../types" import {alwaysValidSchema} from "../../compile/util" const def: CodeKeywordDefinition = { keyword: "metadata", schemaType: "object", code(cxt: KeywordCxt) { checkMetadata(cxt) const {gen, schema, it} = cxt if (alwaysValidSchema(it, schema)) return const valid = gen.name("valid") cxt.subschema({keyword: "metadata", jtdMetadata: true}, valid) cxt.ok(valid) }, } export function checkMetadata({it, keyword}: KeywordCxt, metadata?: boolean): void { if (it.jtdMetadata !== metadata) { throw new Error(`JTD: "${keyword}" cannot be used in this schema location`) } } export default def ================================================ FILE: lib/vocabularies/jtd/nullable.ts ================================================ import type {KeywordCxt} from "../../compile/validate" import {_, not, nil, Code, Name} from "../../compile/codegen" export function checkNullable( {gen, data, parentSchema}: KeywordCxt, cond: Code = nil ): [Name, Code] { const valid = gen.name("valid") if (parentSchema.nullable) { gen.let(valid, _`${data} === null`) cond = not(valid) } else { gen.let(valid, false) } return [valid, cond] } export function checkNullableObject(cxt: KeywordCxt, cond: Code): [Name, Code] { const [valid, cond_] = checkNullable(cxt, cond) return [valid, _`${cond_} && typeof ${cxt.data} == "object" && !Array.isArray(${cxt.data})`] } ================================================ FILE: lib/vocabularies/jtd/optionalProperties.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {validateProperties, error} from "./properties" const def: CodeKeywordDefinition = { keyword: "optionalProperties", schemaType: "object", error, code(cxt: KeywordCxt) { if (cxt.parentSchema.properties) return validateProperties(cxt) }, } export default def ================================================ FILE: lib/vocabularies/jtd/properties.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, SchemaObject, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {propertyInData, allSchemaProperties, isOwnProperty} from "../code" import {alwaysValidSchema, schemaRefOrVal} from "../../compile/util" import {_, and, not, Code, Name} from "../../compile/codegen" import {checkMetadata} from "./metadata" import {checkNullableObject} from "./nullable" import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error" enum PropError { Additional = "additional", Missing = "missing", } type PropKeyword = "properties" | "optionalProperties" type PropSchema = {[P in string]?: SchemaObject} export type JTDPropertiesError = | _JTDTypeError | ErrorObject | ErrorObject export const error: KeywordErrorDefinition = { message: (cxt) => { const {params} = cxt return params.propError ? params.propError === PropError.Additional ? "must NOT have additional properties" : `must have property '${params.missingProperty}'` : typeErrorMessage(cxt, "object") }, params: (cxt) => { const {params} = cxt return params.propError ? params.propError === PropError.Additional ? _`{error: ${params.propError}, additionalProperty: ${params.additionalProperty}}` : _`{error: ${params.propError}, missingProperty: ${params.missingProperty}}` : typeErrorParams(cxt, "object") }, } const def: CodeKeywordDefinition = { keyword: "properties", schemaType: "object", error, code: validateProperties, } // const error: KeywordErrorDefinition = { // message: "should NOT have additional properties", // params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`, // } export function validateProperties(cxt: KeywordCxt): void { checkMetadata(cxt) const {gen, data, parentSchema, it} = cxt const {additionalProperties, nullable} = parentSchema if (it.jtdDiscriminator && nullable) throw new Error("JTD: nullable inside discriminator mapping") if (commonProperties()) { throw new Error("JTD: properties and optionalProperties have common members") } const [allProps, properties] = schemaProperties("properties") const [allOptProps, optProperties] = schemaProperties("optionalProperties") if (properties.length === 0 && optProperties.length === 0 && additionalProperties) { return } const [valid, cond] = it.jtdDiscriminator === undefined ? checkNullableObject(cxt, data) : [gen.let("valid", false), true] gen.if(cond, () => gen.assign(valid, true).block(() => { validateProps(properties, "properties", true) validateProps(optProperties, "optionalProperties") if (!additionalProperties) validateAdditional() }) ) cxt.pass(valid) function commonProperties(): boolean { const props = parentSchema.properties as Record | undefined const optProps = parentSchema.optionalProperties as Record | undefined if (!(props && optProps)) return false for (const p in props) { if (Object.prototype.hasOwnProperty.call(optProps, p)) return true } return false } function schemaProperties(keyword: string): [string[], string[]] { const schema = parentSchema[keyword] const allPs = schema ? allSchemaProperties(schema) : [] if (it.jtdDiscriminator && allPs.some((p) => p === it.jtdDiscriminator)) { throw new Error(`JTD: discriminator tag used in ${keyword}`) } const ps = allPs.filter((p) => !alwaysValidSchema(it, schema[p])) return [allPs, ps] } function validateProps(props: string[], keyword: string, required?: boolean): void { const _valid = gen.var("valid") for (const prop of props) { gen.if( propertyInData(gen, data, prop, it.opts.ownProperties), () => applyPropertySchema(prop, keyword, _valid), () => missingProperty(prop) ) cxt.ok(_valid) } function missingProperty(prop: string): void { if (required) { gen.assign(_valid, false) cxt.error(false, {propError: PropError.Missing, missingProperty: prop}, {schemaPath: prop}) } else { gen.assign(_valid, true) } } } function applyPropertySchema(prop: string, keyword: string, _valid: Name): void { cxt.subschema( { keyword, schemaProp: prop, dataProp: prop, }, _valid ) } function validateAdditional(): void { gen.forIn("key", data, (key: Name) => { const addProp = isAdditional(key, allProps, "properties", it.jtdDiscriminator) const addOptProp = isAdditional(key, allOptProps, "optionalProperties") const extra = addProp === true ? addOptProp : addOptProp === true ? addProp : and(addProp, addOptProp) gen.if(extra, () => { if (it.opts.removeAdditional) { gen.code(_`delete ${data}[${key}]`) } else { cxt.error( false, {propError: PropError.Additional, additionalProperty: key}, {instancePath: key, parentSchema: true} ) if (!it.opts.allErrors) gen.break() } }) }) } function isAdditional( key: Name, props: string[], keyword: string, jtdDiscriminator?: string ): Code | true { let additional: Code | boolean if (props.length > 8) { // TODO maybe an option instead of hard-coded 8? const propsSchema = schemaRefOrVal(it, parentSchema[keyword], keyword) additional = not(isOwnProperty(gen, propsSchema as Code, key)) if (jtdDiscriminator !== undefined) { additional = and(additional, _`${key} !== ${jtdDiscriminator}`) } } else if (props.length || jtdDiscriminator !== undefined) { const ps = jtdDiscriminator === undefined ? props : [jtdDiscriminator].concat(props) additional = and(...ps.map((p) => _`${key} !== ${p}`)) } else { additional = true } return additional } } export default def ================================================ FILE: lib/vocabularies/jtd/ref.ts ================================================ import type {CodeKeywordDefinition, AnySchemaObject} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {compileSchema, SchemaEnv} from "../../compile" import {_, not, nil, stringify} from "../../compile/codegen" import MissingRefError from "../../compile/ref_error" import N from "../../compile/names" import {getValidate, callRef} from "../core/ref" import {checkMetadata} from "./metadata" const def: CodeKeywordDefinition = { keyword: "ref", schemaType: "string", code(cxt: KeywordCxt) { checkMetadata(cxt) const {gen, data, schema: ref, parentSchema, it} = cxt const { schemaEnv: {root}, } = it const valid = gen.name("valid") if (parentSchema.nullable) { gen.var(valid, _`${data} === null`) gen.if(not(valid), validateJtdRef) } else { gen.var(valid, false) validateJtdRef() } cxt.ok(valid) function validateJtdRef(): void { const refSchema = (root.schema as AnySchemaObject).definitions?.[ref] if (!refSchema) { throw new MissingRefError(it.opts.uriResolver, "", ref, `No definition ${ref}`) } if (hasRef(refSchema) || !it.opts.inlineRefs) callValidate(refSchema) else inlineRefSchema(refSchema) } function callValidate(schema: AnySchemaObject): void { const sch = compileSchema.call( it.self, new SchemaEnv({schema, root, schemaPath: `/definitions/${ref}`}) ) const v = getValidate(cxt, sch) const errsCount = gen.const("_errs", N.errors) callRef(cxt, v, sch, sch.$async) gen.assign(valid, _`${errsCount} === ${N.errors}`) } function inlineRefSchema(schema: AnySchemaObject): void { const schName = gen.scopeValue( "schema", it.opts.code.source === true ? {ref: schema, code: stringify(schema)} : {ref: schema} ) cxt.subschema( { schema, dataTypes: [], schemaPath: nil, topSchemaRef: schName, errSchemaPath: `/definitions/${ref}`, }, valid ) } }, } export function hasRef(schema: AnySchemaObject): boolean { for (const key in schema) { let sch: AnySchemaObject if (key === "ref" || (typeof (sch = schema[key]) == "object" && hasRef(sch))) return true } return false } export default def ================================================ FILE: lib/vocabularies/jtd/type.ts ================================================ import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, nil, or, Code} from "../../compile/codegen" import validTimestamp from "../../runtime/timestamp" import {useFunc} from "../../compile/util" import {checkMetadata} from "./metadata" import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error" export type JTDTypeError = _JTDTypeError<"type", JTDType, JTDType> export type IntType = "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" export const intRange: {[T in IntType]: [number, number, number]} = { int8: [-128, 127, 3], uint8: [0, 255, 3], int16: [-32768, 32767, 5], uint16: [0, 65535, 5], int32: [-2147483648, 2147483647, 10], uint32: [0, 4294967295, 10], } export type JTDType = "boolean" | "string" | "timestamp" | "float32" | "float64" | IntType const error: KeywordErrorDefinition = { message: (cxt) => typeErrorMessage(cxt, cxt.schema), params: (cxt) => typeErrorParams(cxt, cxt.schema), } function timestampCode(cxt: KeywordCxt): Code { const {gen, data, it} = cxt const {timestamp, allowDate} = it.opts if (timestamp === "date") return _`${data} instanceof Date ` const vts = useFunc(gen, validTimestamp) const allowDateArg = allowDate ? _`, true` : nil const validString = _`typeof ${data} == "string" && ${vts}(${data}${allowDateArg})` return timestamp === "string" ? validString : or(_`${data} instanceof Date`, validString) } const def: CodeKeywordDefinition = { keyword: "type", schemaType: "string", error, code(cxt: KeywordCxt) { checkMetadata(cxt) const {data, schema, parentSchema, it} = cxt let cond: Code switch (schema) { case "boolean": case "string": cond = _`typeof ${data} == ${schema}` break case "timestamp": { cond = timestampCode(cxt) break } case "float32": case "float64": cond = _`typeof ${data} == "number"` break default: { const sch = schema as IntType cond = _`typeof ${data} == "number" && isFinite(${data}) && !(${data} % 1)` if (!it.opts.int32range && (sch === "int32" || sch === "uint32")) { if (sch === "uint32") cond = _`${cond} && ${data} >= 0` } else { const [min, max] = intRange[sch] cond = _`${cond} && ${data} >= ${min} && ${data} <= ${max}` } } } cxt.pass(parentSchema.nullable ? or(_`${data} === null`, cond) : cond) }, } export default def ================================================ FILE: lib/vocabularies/jtd/union.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import {validateUnion} from "../code" const def: CodeKeywordDefinition = { keyword: "union", schemaType: "array", trackErrors: true, code: validateUnion, error: {message: "must match a schema in union"}, } export default def ================================================ FILE: lib/vocabularies/jtd/values.ts ================================================ import type {CodeKeywordDefinition, SchemaObject} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {alwaysValidSchema, Type} from "../../compile/util" import {not, or, Name} from "../../compile/codegen" import {checkMetadata} from "./metadata" import {checkNullableObject} from "./nullable" import {typeError, _JTDTypeError} from "./error" export type JTDValuesError = _JTDTypeError<"values", "object", SchemaObject> const def: CodeKeywordDefinition = { keyword: "values", schemaType: "object", error: typeError("object"), code(cxt: KeywordCxt) { checkMetadata(cxt) const {gen, data, schema, it} = cxt const [valid, cond] = checkNullableObject(cxt, data) if (alwaysValidSchema(it, schema)) { gen.if(not(or(cond, valid)), () => cxt.error()) } else { gen.if(cond) gen.assign(valid, validateMap()) gen.elseIf(not(valid)) cxt.error() gen.endIf() } cxt.ok(valid) function validateMap(): Name | boolean { const _valid = gen.name("valid") if (it.allErrors) { const validMap = gen.let("valid", true) validateValues(() => gen.assign(validMap, false)) return validMap } gen.var(_valid, true) validateValues(() => gen.break()) return _valid function validateValues(notValid: () => void): void { gen.forIn("key", data, (key) => { cxt.subschema( { keyword: "values", dataProp: key, dataPropType: Type.Str, }, _valid ) gen.if(not(_valid), notValid) }) } } }, } export default def ================================================ FILE: lib/vocabularies/metadata.ts ================================================ import type {Vocabulary} from "../types" export const metadataVocabulary: Vocabulary = [ "title", "description", "default", "deprecated", "readOnly", "writeOnly", "examples", ] export const contentVocabulary: Vocabulary = [ "contentMediaType", "contentEncoding", "contentSchema", ] ================================================ FILE: lib/vocabularies/next.ts ================================================ import type {Vocabulary} from "../types" import dependentRequired from "./validation/dependentRequired" import dependentSchemas from "./applicator/dependentSchemas" import limitContains from "./validation/limitContains" const next: Vocabulary = [dependentRequired, dependentSchemas, limitContains] export default next ================================================ FILE: lib/vocabularies/unevaluated/index.ts ================================================ import type {Vocabulary} from "../../types" import unevaluatedProperties from "./unevaluatedProperties" import unevaluatedItems from "./unevaluatedItems" const unevaluated: Vocabulary = [unevaluatedProperties, unevaluatedItems] export default unevaluated ================================================ FILE: lib/vocabularies/unevaluated/unevaluatedItems.ts ================================================ import type { CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition, AnySchema, } from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, not, Name} from "../../compile/codegen" import {alwaysValidSchema, Type} from "../../compile/util" export type UnevaluatedItemsError = ErrorObject<"unevaluatedItems", {limit: number}, AnySchema> const error: KeywordErrorDefinition = { message: ({params: {len}}) => str`must NOT have more than ${len} items`, params: ({params: {len}}) => _`{limit: ${len}}`, } const def: CodeKeywordDefinition = { keyword: "unevaluatedItems", type: "array", schemaType: ["boolean", "object"], error, code(cxt: KeywordCxt) { const {gen, schema, data, it} = cxt const items = it.items || 0 if (items === true) return const len = gen.const("len", _`${data}.length`) if (schema === false) { cxt.setParams({len: items}) cxt.fail(_`${len} > ${items}`) } else if (typeof schema == "object" && !alwaysValidSchema(it, schema)) { const valid = gen.var("valid", _`${len} <= ${items}`) gen.if(not(valid), () => validateItems(valid, items)) cxt.ok(valid) } it.items = true function validateItems(valid: Name, from: Name | number): void { gen.forRange("i", from, len, (i) => { cxt.subschema({keyword: "unevaluatedItems", dataProp: i, dataPropType: Type.Num}, valid) if (!it.allErrors) gen.if(not(valid), () => gen.break()) }) } }, } export default def ================================================ FILE: lib/vocabularies/unevaluated/unevaluatedProperties.ts ================================================ import type { CodeKeywordDefinition, KeywordErrorDefinition, ErrorObject, AnySchema, } from "../../types" import {_, not, and, Name, Code} from "../../compile/codegen" import {alwaysValidSchema, Type} from "../../compile/util" import N from "../../compile/names" export type UnevaluatedPropertiesError = ErrorObject< "unevaluatedProperties", {unevaluatedProperty: string}, AnySchema > const error: KeywordErrorDefinition = { message: "must NOT have unevaluated properties", params: ({params}) => _`{unevaluatedProperty: ${params.unevaluatedProperty}}`, } const def: CodeKeywordDefinition = { keyword: "unevaluatedProperties", type: "object", schemaType: ["boolean", "object"], trackErrors: true, error, code(cxt) { const {gen, schema, data, errsCount, it} = cxt /* istanbul ignore if */ if (!errsCount) throw new Error("ajv implementation error") const {allErrors, props} = it if (props instanceof Name) { gen.if(_`${props} !== true`, () => gen.forIn("key", data, (key: Name) => gen.if(unevaluatedDynamic(props, key), () => unevaluatedPropCode(key)) ) ) } else if (props !== true) { gen.forIn("key", data, (key: Name) => props === undefined ? unevaluatedPropCode(key) : gen.if(unevaluatedStatic(props, key), () => unevaluatedPropCode(key)) ) } it.props = true cxt.ok(_`${errsCount} === ${N.errors}`) function unevaluatedPropCode(key: Name): void { if (schema === false) { cxt.setParams({unevaluatedProperty: key}) cxt.error() if (!allErrors) gen.break() return } if (!alwaysValidSchema(it, schema)) { const valid = gen.name("valid") cxt.subschema( { keyword: "unevaluatedProperties", dataProp: key, dataPropType: Type.Str, }, valid ) if (!allErrors) gen.if(not(valid), () => gen.break()) } } function unevaluatedDynamic(evaluatedProps: Name, key: Name): Code { return _`!${evaluatedProps} || !${evaluatedProps}[${key}]` } function unevaluatedStatic(evaluatedProps: {[K in string]?: true}, key: Name): Code { const ps: Code[] = [] for (const p in evaluatedProps) { if (evaluatedProps[p] === true) ps.push(_`${key} !== ${p}`) } return and(...ps) } }, } export default def ================================================ FILE: lib/vocabularies/validation/const.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_} from "../../compile/codegen" import {useFunc} from "../../compile/util" import equal from "../../runtime/equal" export type ConstError = ErrorObject<"const", {allowedValue: any}> const error: KeywordErrorDefinition = { message: "must be equal to constant", params: ({schemaCode}) => _`{allowedValue: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: "const", $data: true, error, code(cxt: KeywordCxt) { const {gen, data, $data, schemaCode, schema} = cxt if ($data || (schema && typeof schema == "object")) { cxt.fail$data(_`!${useFunc(gen, equal)}(${data}, ${schemaCode})`) } else { cxt.fail(_`${schema} !== ${data}`) } }, } export default def ================================================ FILE: lib/vocabularies/validation/dependentRequired.ts ================================================ import type {CodeKeywordDefinition, ErrorObject} from "../../types" import { validatePropertyDeps, error, DependenciesErrorParams, PropertyDependencies, } from "../applicator/dependencies" export type DependentRequiredError = ErrorObject< "dependentRequired", DependenciesErrorParams, PropertyDependencies > const def: CodeKeywordDefinition = { keyword: "dependentRequired", type: "object", schemaType: "object", error, code: (cxt) => validatePropertyDeps(cxt), } export default def ================================================ FILE: lib/vocabularies/validation/enum.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, or, Name, Code} from "../../compile/codegen" import {useFunc} from "../../compile/util" import equal from "../../runtime/equal" export type EnumError = ErrorObject<"enum", {allowedValues: any[]}, any[] | {$data: string}> const error: KeywordErrorDefinition = { message: "must be equal to one of the allowed values", params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: "enum", schemaType: "array", $data: true, error, code(cxt: KeywordCxt) { const {gen, data, $data, schema, schemaCode, it} = cxt if (!$data && schema.length === 0) throw new Error("enum must have non-empty array") const useLoop = schema.length >= it.opts.loopEnum let eql: Name | undefined const getEql = (): Name => (eql ??= useFunc(gen, equal)) let valid: Code if (useLoop || $data) { valid = gen.let("valid") cxt.block$data(valid, loopEnum) } else { /* istanbul ignore if */ if (!Array.isArray(schema)) throw new Error("ajv implementation error") const vSchema = gen.const("vSchema", schemaCode) valid = or(...schema.map((_x: unknown, i: number) => equalCode(vSchema, i))) } cxt.pass(valid) function loopEnum(): void { gen.assign(valid, false) gen.forOf("v", schemaCode as Code, (v) => gen.if(_`${getEql()}(${data}, ${v})`, () => gen.assign(valid, true).break()) ) } function equalCode(vSchema: Name, i: number): Code { const sch = schema[i] return typeof sch === "object" && sch !== null ? _`${getEql()}(${data}, ${vSchema}[${i}])` : _`${data} === ${sch}` } }, } export default def ================================================ FILE: lib/vocabularies/validation/index.ts ================================================ import type {ErrorObject, Vocabulary} from "../../types" import limitNumber, {LimitNumberError} from "./limitNumber" import multipleOf, {MultipleOfError} from "./multipleOf" import limitLength from "./limitLength" import pattern, {PatternError} from "./pattern" import limitProperties from "./limitProperties" import required, {RequiredError} from "./required" import limitItems from "./limitItems" import uniqueItems, {UniqueItemsError} from "./uniqueItems" import constKeyword, {ConstError} from "./const" import enumKeyword, {EnumError} from "./enum" const validation: Vocabulary = [ // number limitNumber, multipleOf, // string limitLength, pattern, // object limitProperties, required, // array limitItems, uniqueItems, // any {keyword: "type", schemaType: ["string", "array"]}, {keyword: "nullable", schemaType: "boolean"}, constKeyword, enumKeyword, ] export default validation type LimitError = ErrorObject< "maxItems" | "minItems" | "minProperties" | "maxProperties" | "minLength" | "maxLength", {limit: number}, number | {$data: string} > export type ValidationKeywordError = | LimitError | LimitNumberError | MultipleOfError | PatternError | RequiredError | UniqueItemsError | ConstError | EnumError ================================================ FILE: lib/vocabularies/validation/limitContains.ts ================================================ import type {CodeKeywordDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {checkStrictMode} from "../../compile/util" const def: CodeKeywordDefinition = { keyword: ["maxContains", "minContains"], type: "array", schemaType: "number", code({keyword, parentSchema, it}: KeywordCxt) { if (parentSchema.contains === undefined) { checkStrictMode(it, `"${keyword}" without "contains" is ignored`) } }, } export default def ================================================ FILE: lib/vocabularies/validation/limitItems.ts ================================================ import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, operators} from "../../compile/codegen" const error: KeywordErrorDefinition = { message({keyword, schemaCode}) { const comp = keyword === "maxItems" ? "more" : "fewer" return str`must NOT have ${comp} than ${schemaCode} items` }, params: ({schemaCode}) => _`{limit: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: ["maxItems", "minItems"], type: "array", schemaType: "number", $data: true, error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode} = cxt const op = keyword === "maxItems" ? operators.GT : operators.LT cxt.fail$data(_`${data}.length ${op} ${schemaCode}`) }, } export default def ================================================ FILE: lib/vocabularies/validation/limitLength.ts ================================================ import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, operators} from "../../compile/codegen" import {useFunc} from "../../compile/util" import ucs2length from "../../runtime/ucs2length" const error: KeywordErrorDefinition = { message({keyword, schemaCode}) { const comp = keyword === "maxLength" ? "more" : "fewer" return str`must NOT have ${comp} than ${schemaCode} characters` }, params: ({schemaCode}) => _`{limit: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: ["maxLength", "minLength"], type: "string", schemaType: "number", $data: true, error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode, it} = cxt const op = keyword === "maxLength" ? operators.GT : operators.LT const len = it.opts.unicode === false ? _`${data}.length` : _`${useFunc(cxt.gen, ucs2length)}(${data})` cxt.fail$data(_`${len} ${op} ${schemaCode}`) }, } export default def ================================================ FILE: lib/vocabularies/validation/limitNumber.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, operators, Code} from "../../compile/codegen" const ops = operators type Kwd = "maximum" | "minimum" | "exclusiveMaximum" | "exclusiveMinimum" type Comparison = "<=" | ">=" | "<" | ">" const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = { maximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT}, minimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT}, exclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE}, exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE}, } export type LimitNumberError = ErrorObject< Kwd, {limit: number; comparison: Comparison}, number | {$data: string} > const error: KeywordErrorDefinition = { message: ({keyword, schemaCode}) => str`must be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`, params: ({keyword, schemaCode}) => _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: Object.keys(KWDs), type: "number", schemaType: "number", $data: true, error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode} = cxt cxt.fail$data(_`${data} ${KWDs[keyword as Kwd].fail} ${schemaCode} || isNaN(${data})`) }, } export default def ================================================ FILE: lib/vocabularies/validation/limitProperties.ts ================================================ import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str, operators} from "../../compile/codegen" const error: KeywordErrorDefinition = { message({keyword, schemaCode}) { const comp = keyword === "maxProperties" ? "more" : "fewer" return str`must NOT have ${comp} than ${schemaCode} properties` }, params: ({schemaCode}) => _`{limit: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: ["maxProperties", "minProperties"], type: "object", schemaType: "number", $data: true, error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode} = cxt const op = keyword === "maxProperties" ? operators.GT : operators.LT cxt.fail$data(_`Object.keys(${data}).length ${op} ${schemaCode}`) }, } export default def ================================================ FILE: lib/vocabularies/validation/multipleOf.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {_, str} from "../../compile/codegen" export type MultipleOfError = ErrorObject< "multipleOf", {multipleOf: number}, number | {$data: string} > const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`must be multiple of ${schemaCode}`, params: ({schemaCode}) => _`{multipleOf: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: "multipleOf", type: "number", schemaType: "number", $data: true, error, code(cxt: KeywordCxt) { const {gen, data, schemaCode, it} = cxt // const bdt = bad$DataType(schemaCode, def.schemaType, $data) const prec = it.opts.multipleOfPrecision const res = gen.let("res") const invalid = prec ? _`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : _`${res} !== parseInt(${res})` cxt.fail$data(_`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`) }, } export default def ================================================ FILE: lib/vocabularies/validation/pattern.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {usePattern} from "../code" import {useFunc} from "../../compile/util" import {_, str} from "../../compile/codegen" export type PatternError = ErrorObject<"pattern", {pattern: string}, string | {$data: string}> const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`must match pattern "${schemaCode}"`, params: ({schemaCode}) => _`{pattern: ${schemaCode}}`, } const def: CodeKeywordDefinition = { keyword: "pattern", type: "string", schemaType: "string", $data: true, error, code(cxt: KeywordCxt) { const {gen, data, $data, schema, schemaCode, it} = cxt const u = it.opts.unicodeRegExp ? "u" : "" if ($data) { const {regExp} = it.opts.code const regExpCode = regExp.code === "new RegExp" ? _`new RegExp` : useFunc(gen, regExp) const valid = gen.let("valid") gen.try( () => gen.assign(valid, _`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false) ) cxt.fail$data(_`!${valid}`) } else { const regExp = usePattern(cxt, schema) cxt.fail$data(_`!${regExp}.test(${data})`) } }, } export default def ================================================ FILE: lib/vocabularies/validation/required.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import { checkReportMissingProp, checkMissingProp, reportMissingProp, propertyInData, noPropertyInData, } from "../code" import {_, str, nil, not, Name, Code} from "../../compile/codegen" import {checkStrictMode} from "../../compile/util" export type RequiredError = ErrorObject< "required", {missingProperty: string}, string[] | {$data: string} > const error: KeywordErrorDefinition = { message: ({params: {missingProperty}}) => str`must have required property '${missingProperty}'`, params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`, } const def: CodeKeywordDefinition = { keyword: "required", type: "object", schemaType: "array", $data: true, error, code(cxt: KeywordCxt) { const {gen, schema, schemaCode, data, $data, it} = cxt const {opts} = it if (!$data && schema.length === 0) return const useLoop = schema.length >= opts.loopRequired if (it.allErrors) allErrorsMode() else exitOnErrorMode() if (opts.strictRequired) { const props = cxt.parentSchema.properties const {definedProperties} = cxt.it for (const requiredKey of schema) { if (props?.[requiredKey] === undefined && !definedProperties.has(requiredKey)) { const schemaPath = it.schemaEnv.baseId + it.errSchemaPath const msg = `required property "${requiredKey}" is not defined at "${schemaPath}" (strictRequired)` checkStrictMode(it, msg, it.opts.strictRequired) } } } function allErrorsMode(): void { if (useLoop || $data) { cxt.block$data(nil, loopAllRequired) } else { for (const prop of schema) { checkReportMissingProp(cxt, prop) } } } function exitOnErrorMode(): void { const missing = gen.let("missing") if (useLoop || $data) { const valid = gen.let("valid", true) cxt.block$data(valid, () => loopUntilMissing(missing, valid)) cxt.ok(valid) } else { gen.if(checkMissingProp(cxt, schema, missing)) reportMissingProp(cxt, missing) gen.else() } } function loopAllRequired(): void { gen.forOf("prop", schemaCode as Code, (prop) => { cxt.setParams({missingProperty: prop}) gen.if(noPropertyInData(gen, data, prop, opts.ownProperties), () => cxt.error()) }) } function loopUntilMissing(missing: Name, valid: Name): void { cxt.setParams({missingProperty: missing}) gen.forOf( missing, schemaCode as Code, () => { gen.assign(valid, propertyInData(gen, data, missing, opts.ownProperties)) gen.if(not(valid), () => { cxt.error() gen.break() }) }, nil ) } }, } export default def ================================================ FILE: lib/vocabularies/validation/uniqueItems.ts ================================================ import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type {KeywordCxt} from "../../compile/validate" import {checkDataTypes, getSchemaTypes, DataType} from "../../compile/validate/dataType" import {_, str, Name} from "../../compile/codegen" import {useFunc} from "../../compile/util" import equal from "../../runtime/equal" export type UniqueItemsError = ErrorObject< "uniqueItems", {i: number; j: number}, boolean | {$data: string} > const error: KeywordErrorDefinition = { message: ({params: {i, j}}) => str`must NOT have duplicate items (items ## ${j} and ${i} are identical)`, params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`, } const def: CodeKeywordDefinition = { keyword: "uniqueItems", type: "array", schemaType: "boolean", $data: true, error, code(cxt: KeywordCxt) { const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt if (!$data && !schema) return const valid = gen.let("valid") const itemTypes = parentSchema.items ? getSchemaTypes(parentSchema.items) : [] cxt.block$data(valid, validateUniqueItems, _`${schemaCode} === false`) cxt.ok(valid) function validateUniqueItems(): void { const i = gen.let("i", _`${data}.length`) const j = gen.let("j") cxt.setParams({i, j}) gen.assign(valid, true) gen.if(_`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j)) } function canOptimize(): boolean { return itemTypes.length > 0 && !itemTypes.some((t) => t === "object" || t === "array") } function loopN(i: Name, j: Name): void { const item = gen.name("item") const wrongType = checkDataTypes(itemTypes, item, it.opts.strictNumbers, DataType.Wrong) const indices = gen.const("indices", _`{}`) gen.for(_`;${i}--;`, () => { gen.let(item, _`${data}[${i}]`) gen.if(wrongType, _`continue`) if (itemTypes.length > 1) gen.if(_`typeof ${item} == "string"`, _`${item} += "_"`) gen .if(_`typeof ${indices}[${item}] == "number"`, () => { gen.assign(j, _`${indices}[${item}]`) cxt.error() gen.assign(valid, false).break() }) .code(_`${indices}[${item}] = ${i}`) }) } function loopN2(i: Name, j: Name): void { const eql = useFunc(gen, equal) const outer = gen.name("outer") gen.label(outer).for(_`;${i}--;`, () => gen.for(_`${j} = ${i}; ${j}--;`, () => gen.if(_`${eql}(${data}[${i}], ${data}[${j}])`, () => { cxt.error() gen.assign(valid, false).break(outer) }) ) ) } }, } export default def ================================================ FILE: package.json ================================================ { "name": "ajv", "version": "8.18.0", "description": "Another JSON Schema Validator", "main": "dist/ajv.js", "types": "dist/ajv.d.ts", "files": [ "lib/", "dist/", ".runkit_example.js" ], "sideEffects": false, "scripts": { "eslint": "eslint \"lib/**/*.ts\" \"spec/**/*.*s\" --ignore-pattern spec/JSON-Schema-Test-Suite", "prettier:write": "prettier --write \"./**/*.{json,yaml,js,ts}\"", "prettier:check": "prettier --list-different \"./**/*.{json,yaml,js,ts}\"", "test-spec": "cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register \"spec/**/*.spec.{ts,js}\" -R dot", "test-codegen": "nyc cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register 'spec/codegen.spec.ts' -R spec", "test-debug": "npm run test-spec -- --inspect-brk", "test-cov": "nyc npm run test-spec", "rollup": "rm -rf bundle && rollup -c", "bundle": "rm -rf bundle && node ./scripts/bundle.js ajv ajv7 ajv7 && node ./scripts/bundle.js 2019 ajv2019 ajv2019 && node ./scripts/bundle.js 2020 ajv2020 ajv2020 && node ./scripts/bundle.js jtd ajvJTD ajvJTD", "build": "rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts && rm dist/refs/json-schema-2020-12/index.ts && rm dist/refs/jtd-schema.ts", "json-tests": "rm -rf spec/_json/*.js && node scripts/jsontests", "test-karma": "karma start", "test-browser": "rm -rf .browser && npm run bundle && scripts/prepare-tests && karma start", "test-all": "npm run test-cov && if-node-version 12 npm run test-browser", "test": "npm run json-tests && npm run prettier:check && npm run eslint && npm link && npm link --legacy-peer-deps ajv && npm run test-cov", "test-ci": "AJV_FULL_TEST=true npm test", "prepublish": "npm run build", "benchmark": "npm i && npm run build && npm link && cd ./benchmark && npm link --legacy-peer-deps ajv && npm i && node ./jtd", "docs:dev": "./scripts/prepare-site && vuepress dev docs", "docs:build": "./scripts/prepare-site && vuepress build docs" }, "nyc": { "exclude": [ "**/spec/**", "node_modules" ], "reporter": [ "lcov", "text-summary" ] }, "repository": "ajv-validator/ajv", "keywords": [ "JSON", "schema", "validator", "validation", "jsonschema", "json-schema", "json-schema-validator", "json-schema-validation" ], "author": "Evgeny Poberezkin", "license": "MIT", "bugs": "https://github.com/ajv-validator/ajv/issues", "homepage": "https://ajv.js.org", "runkitExampleFilename": ".runkit_example.js", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" }, "devDependencies": { "@ajv-validator/config": "^0.5.0", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", "@types/node": "^20.11.30", "@types/require-from-string": "^1.2.3", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", "ajv-formats": "^3.0.1", "browserify": "^17.0.0", "chai": "^4.4.1", "cross-env": "^7.0.3", "dayjs": "^1.11.10", "dayjs-plugin-utc": "^0.1.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "glob": "^10.3.10", "husky": "^9.0.11", "if-node-version": "^1.1.1", "jimp": "^0.22.10", "js-beautify": "^1.15.1", "json-schema-test": "^2.0.0", "karma": "^6.4.2", "karma-chrome-launcher": "^3.2.0", "karma-mocha": "^2.0.1", "lint-staged": "^15.2.2", "mocha": "^10.3.0", "module-from-string": "^3.3.0", "node-fetch": "^3.3.2", "nyc": "^15.1.0", "prettier": "3.0.3", "re2": "^1.20.9", "rollup": "^2.79.1", "rollup-plugin-terser": "^7.0.2", "ts-node": "^10.9.2", "tsify": "^5.0.4", "typescript": "5.3.3", "uri-js": "^4.4.1" }, "collective": { "type": "opencollective", "url": "https://opencollective.com/ajv" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" }, "prettier": "@ajv-validator/config/prettierrc.json", "husky": { "hooks": { "pre-commit": "lint-staged && npm test" } }, "lint-staged": { "*.{json,yaml,js,ts}": "prettier --write" } } ================================================ FILE: rollup.config.js ================================================ import commonjs from "@rollup/plugin-commonjs" import {nodeResolve} from "@rollup/plugin-node-resolve" import json from "@rollup/plugin-json" import typescript from "@rollup/plugin-typescript" import {terser} from "rollup-plugin-terser" function createBundleConfig(sourceFile, outFile, globalName) { return { input: `./lib/${sourceFile}.ts`, output: [ { file: `./bundle/${outFile}.bundle.js`, format: "umd", name: globalName, }, { file: `./bundle/${outFile}.min.js`, format: "umd", name: globalName, sourcemap: true, plugins: [terser()], }, ], plugins: [commonjs(), nodeResolve(), json(), typescript()], } } export default [ createBundleConfig("ajv", "ajv7", "ajv7"), createBundleConfig("2019", "ajv2019", "ajv2019"), createBundleConfig("2020", "ajv2020", "ajv2020"), createBundleConfig("jtd", "ajvJTD", "ajvJTD"), ] ================================================ FILE: scripts/.eslintrc.yml ================================================ parserOptions: sourceType: script rules: no-console: 0 no-empty: [2, allowEmptyCatch: true] ================================================ FILE: scripts/bundle.js ================================================ "use strict" const fs = require("fs") const path = require("path") const browserify = require("browserify") const {minify} = require("terser") const [sourceFile, outFile, globalName] = process.argv.slice(2) const json = require(path.join(__dirname, "..", "package.json")) const bundleDir = path.join(__dirname, "..", "bundle") if (!fs.existsSync(bundleDir)) fs.mkdirSync(bundleDir) browserify({standalone: globalName}) .require(path.join(__dirname, "../dist", sourceFile), {expose: sourceFile}) .bundle(saveAndMinify) async function saveAndMinify(err, buf) { if (err) { console.error("browserify error:", err) process.exit(1) } const bundlePath = path.join(bundleDir, outFile) const opts = { ecma: 2018, warnings: true, compress: { pure_getters: true, keep_infinity: true, unsafe_methods: true, }, format: { preamble: `/* ${json.name} ${json.version} (${globalName}): ${json.description} */`, }, sourceMap: { filename: outFile + ".min.js", url: outFile + ".min.js.map", }, } const result = await minify(buf.toString(), opts) fs.writeFileSync(bundlePath + ".bundle.js", buf) fs.writeFileSync(bundlePath + ".min.js", result.code) fs.writeFileSync(bundlePath + ".min.js.map", result.map) if (result.warnings) result.warnings.forEach((msg) => console.warn("terser.minify warning:", msg)) } ================================================ FILE: scripts/get-ajv-packages ================================================ #!/usr/bin/env bash declare -a packages=( "ajv-keywords" "ajv-formats" "ajv-cli" "ajv-errors" "ajv-i18n" ) for package in "${packages[@]}" do echo "downloading $package README..." echo "---" > docs/packages/$package.md echo "editLink: https://github.com/ajv-validator/$package/edit/master/README.md" >> docs/packages/$package.md echo "---" >> docs/packages/$package.md echo "[$package repository](https://github.com/ajv-validator/$package)" >> docs/packages/$package.md echo "" >> docs/packages/$package.md curl -L https://raw.githubusercontent.com/ajv-validator/$package/master/README.md >> ./docs/packages/$package.md done ================================================ FILE: scripts/get-contributors.js ================================================ // Credit for the script goes to svelte: // https://github.com/sveltejs/svelte/blob/ce3a5791258ec6ecf8c1ea022cb871afe805a45c/site/scripts/get-contributors.js const fs = require("fs") const Jimp = require("jimp") process.chdir(__dirname) const base = `https://api.github.com/repos/ajv-validator/ajv/contributors` const {GH_TOKEN_PUBLIC} = process.env const SIZE = 64 main() async function main() { const fetch = (await import("node-fetch")).default const contributors = [] let page = 1 // eslint-disable-next-line no-constant-condition while (true) { const res = await fetch(`${base}?per_page=100&page=${page++}`, { headers: {Authorization: `token ${GH_TOKEN_PUBLIC}`}, }) const list = await res.json() if (list.length === 0) break contributors.push(...list) } const bots = ["dependabot-preview[bot]", "greenkeeper[bot]", "greenkeeperio-bot"] const authors = contributors .filter((a) => !bots.includes(a.login)) .sort((a, b) => b.contributions - a.contributions) const sprite = new Jimp(SIZE * authors.length, SIZE) for (let i = 0; i < authors.length; i += 1) { const author = authors[i] console.log(`${i + 1} / ${authors.length}: ${author.login}`) const image_data = await fetch(author.avatar_url) const buffer = await image_data.arrayBuffer() const image = await Jimp.read(buffer) image.resize(SIZE, SIZE) sprite.composite(image, i * SIZE, 0) } await sprite.quality(80).write(`../docs/.vuepress/components/Contributors/contributors.jpg`) const str = `[\n ${authors.map((a) => `"${a.login}"`).join(",\n ")},\n]\n` fs.writeFileSync( `../docs/.vuepress/components/Contributors/_contributors.js`, `module.exports = ${str}` ) } ================================================ FILE: scripts/jsontests.js ================================================ "use strict" const testSuitePaths = { draft6: "spec/JSON-Schema-Test-Suite/tests/draft6/", draft7: "spec/JSON-Schema-Test-Suite/tests/draft7/", draft2019: "spec/JSON-Schema-Test-Suite/tests/draft2019-09/", draft2020: "spec/JSON-Schema-Test-Suite/tests/draft2020-12/", tests: "spec/tests/", security: "spec/security/", extras: "spec/extras/", async: "spec/async/", } const glob = require("glob") const fs = require("fs") for (const suite in testSuitePaths) { const p = testSuitePaths[suite] const files = glob.sync(`${p}{**/,}*.json`) if (files.length === 0) { console.error(`Missing folder ${p}\nTry: git submodule update --init\n`) process.exit(1) } const code = files .map((f) => { const name = f.replace(p, "").replace(/\.json$/, "") const testPath = f.replace(/^spec/, "..") return `\n {name: "${name}", test: require("${testPath}")},` }) .reduce((list, f) => list + f) fs.writeFileSync(`./spec/_json/${suite}.js`, `module.exports = [${code}\n]\n`) } ================================================ FILE: scripts/prepare-site ================================================ #!/usr/bin/env bash set -e function copyReplace { sed "s/](.\/docs\//](.\//g" $1 > $2 } copyReplace CODE_OF_CONDUCT.md docs/code_of_conduct.md copyReplace CONTRIBUTING.md docs/contributing.md copyReplace LICENSE docs/license.md echo "preparing contributors..." node scripts/get-contributors.js echo "preparing packages..." ./scripts/get-ajv-packages ================================================ FILE: scripts/prepare-tests ================================================ #!/usr/bin/env sh set -e mkdir -p .browser echo echo Preparing browser tests: find spec -type f -name '*.spec.*s' | \ xargs -I {} sh -c \ 'export f="{}"; echo $f; ./node_modules/.bin/browserify $f -p [ tsify -p ./spec/tsconfig.json ] -x ajv -u buffer -o $(echo $f | sed -e "s/spec/.browser/" | sed -e "s/.spec.ts/.spec.js/");' ================================================ FILE: scripts/publish-bundles ================================================ #!/usr/bin/env bash set -e if [[ $GITHUB_REF == refs/tags/v* ]]; then npm run bundle echo "About to publish $GITHUB_REF to ajv-dist..." git config --global user.name "$GIT_USER_NAME" git config --global user.email "$GIT_USER_EMAIL" git clone https://"${GH_TOKEN_PUBLIC}"@github.com/ajv-validator/ajv-dist.git ../ajv-dist rm -rf ../ajv-dist/dist mkdir ../ajv-dist/dist cp ./bundle/*.* ../ajv-dist/dist cd ../ajv-dist VERSION=${GITHUB_REF#refs/tags/v} sed -E "s/\"version\": \"([^\"]*)\"/\"version\": \"$VERSION\"/" package.json > new_package.json mv new_package.json package.json if [[ $(git status --porcelain) ]]; then echo "Changes detected. Updating master branch..." git add -A git commit -m "$VERSION: updated by ajv workflow https://github.com/ajv-validator/ajv/actions/runs/$GITHUB_RUN_ID" git push --quiet origin master > /dev/null 2>&1 fi echo "Publishing tag..." git tag "v$VERSION" git push --tags > /dev/null 2>&1 echo "Done" fi ================================================ FILE: scripts/publish-site ================================================ #!/usr/bin/env bash set -ex echo "About to publish $GITHUB_REF to gh-pages..." rm -rf ../gh-pages npm install vuepress@1 npm install @vuepress/shared-utils@1 npm run docs:build git config --global user.name "$GIT_USER_NAME" git config --global user.email "$GIT_USER_EMAIL" git clone -b gh-pages --single-branch https://"${GH_TOKEN_PUBLIC}"@github.com/ajv-validator/ajv.git ../gh-pages rsync -a ./docs/.vuepress/dist/ ../gh-pages cd ../gh-pages if [[ $(git status --porcelain) ]]; then echo "Changes detected. Updating gh-pages branch..." git add -A git commit -m "updated by ajv workflow https://github.com/ajv-validator/ajv/actions/runs/$GITHUB_RUN_ID" git push --quiet origin gh-pages > /dev/null 2>&1 fi echo "Done" ================================================ FILE: spec/.eslintrc.yml ================================================ env: browser: true, rules: no-console: off no-invalid-this: off globals: describe: false it: false before: false beforeEach: false afterEach: false overrides: - files: ["*.ts"] parserOptions: project: ["./spec/tsconfig.json"] rules: "@typescript-eslint/explicit-function-return-type": off "@typescript-eslint/explicit-member-accessibility": off "@typescript-eslint/restrict-plus-operands": off "@typescript-eslint/no-explicit-any": off "@typescript-eslint/no-unsafe-assignment": off "@typescript-eslint/no-unsafe-call": off "@typescript-eslint/no-unsafe-member-access": off "@typescript-eslint/no-unsafe-return": off "@typescript-eslint/no-var-requires": off "@typescript-eslint/ban-ts-comment": off ================================================ FILE: spec/_json/README.md ================================================ # Test suites from JSON tests These files are generated automatically during the test. ================================================ FILE: spec/after_test.ts ================================================ import type Ajv from ".." import type {AnySchema, ErrorObject} from ".." import chai from "./chai" const should = chai.should() interface TestResult { validator: Ajv schema: AnySchema data: unknown valid: boolean expected: boolean errors: ErrorObject[] | null passed: boolean // true if valid == expected } export function afterError(res: TestResult): void { console.log("ajv options:", res.validator.opts) } export function afterEach(res: TestResult): void { // console.log(res.errors); res.valid.should.be.a("boolean") if (res.valid === true) { should.equal(res.errors, null) } else { const errs = res.errors as ErrorObject[] errs.should.be.an("array") for (const err of errs) { err.should.be.an("object") } } } ================================================ FILE: spec/ajv.spec.ts ================================================ import type Ajv from ".." import type {KeywordCxt, SchemaObject} from ".." import _Ajv from "./ajv" import {_} from "../dist/compile/codegen/code" import assert = require("assert") import chai from "./chai" const should = chai.should() describe("Ajv", () => { let ajv: Ajv beforeEach(() => { ajv = new _Ajv({keywords: ["foo"], allowUnionTypes: true}) }) it("should create instance", () => { ajv.should.be.instanceof(_Ajv) }) describe("compile method", () => { it("should compile schema and return validating function", () => { const validate = ajv.compile({type: "integer"}) validate.should.be.a("function") validate(1).should.equal(true) validate(1.1).should.equal(false) validate("1").should.equal(false) }) it("should cache compiled functions for the same schema", () => { const schema = { $id: "//e.com/int.json", type: "integer", minimum: 1, } const v1 = ajv.compile(schema) const v2 = ajv.compile(schema) v1.should.equal(v2) }) it("should throw if different schema has the same id", () => { ajv.compile({$id: "//e.com/int.json", type: "integer"}) should.throw(() => { ajv.compile({$id: "//e.com/int.json", type: "integer", minimum: 1}) }, /already exists/) }) it("should throw if invalid schema is compiled", () => { should.throw(() => { ajv.compile({type: null}) }, /must be equal to one of the allowed values/) }) it("should throw if compiled schema has an invalid JavaScript code", () => { const _ajv = new _Ajv({logger: false}) _ajv.addKeyword({keyword: "even", code: badEvenCode}) let schema = {even: true} const validate: any = _ajv.compile(schema) validate(2).should.equal(true) validate(3).should.equal(false) schema = {even: false} should.throw(() => { _ajv.compile(schema) }, /Unexpected token/) function badEvenCode(cxt: KeywordCxt) { const op = cxt.schema ? _`===` : _`!===` // invalid on purpose cxt.pass(_`${cxt.data} % 2 ${op} 0`) } }) }) describe("validate method", () => { it("should compile schema and validate data against it", () => { ajv.validate({type: "integer"}, 1).should.equal(true) ajv.validate({type: "integer"}, "1").should.equal(false) ajv.validate({type: "string"}, "a").should.equal(true) ajv.validate({type: "string"}, 1).should.equal(false) }) it("should validate against previously compiled schema by id (also see addSchema)", () => { ajv.validate({$id: "//e.com/int.json", type: "integer"}, 1).should.equal(true) ajv.validate("//e.com/int.json", 1).should.equal(true) ajv.validate("//e.com/int.json", "1").should.equal(false) ajv.compile({$id: "//e.com/str.json", type: "string"}).should.be.a("function") ajv.validate("//e.com/str.json", "a").should.equal(true) ajv.validate("//e.com/str.json", 1).should.equal(false) }) it("should throw exception if no schema with ref", () => { ajv.validate({$id: "integer", type: "integer"}, 1).should.equal(true) ajv.validate("integer", 1).should.equal(true) should.throw(() => { ajv.validate("string", "foo") }, /no schema with key or ref/) }) it("should validate schema fragment by ref", () => { ajv.addSchema({ $id: "http://e.com/types.json", definitions: { int: {type: "integer"}, str: {type: "string"}, }, }) ajv.validate("http://e.com/types.json#/definitions/int", 1).should.equal(true) ajv.validate("http://e.com/types.json#/definitions/int", "1").should.equal(false) }) it("should return schema fragment by id", () => { ajv.addSchema({ $id: "http://e.com/types.json", definitions: { int: {$id: "#int", type: "integer"}, str: {$id: "#str", type: "string"}, }, }) ajv.validate("http://e.com/types.json#int", 1).should.equal(true) ajv.validate("http://e.com/types.json#int", "1").should.equal(false) }) }) describe("addSchema method", () => { it("should add and compile schema with key", () => { ajv.addSchema({type: "integer"}, "int") const validate = ajv.getSchema("int") assert(typeof validate == "function") validate(1).should.equal(true) validate(1.1).should.equal(false) validate("1").should.equal(false) ajv.validate("int", 1).should.equal(true) ajv.validate("int", "1").should.equal(false) }) it("should add and compile schema without key", () => { ajv.addSchema({type: "integer"}) ajv.validate("", 1).should.equal(true) ajv.validate("", "1").should.equal(false) }) it("should add and compile schema with id", () => { ajv.addSchema({$id: "//e.com/int.json", type: "integer"}) ajv.validate("//e.com/int.json", 1).should.equal(true) ajv.validate("//e.com/int.json", "1").should.equal(false) }) it("should normalize schema keys and ids", () => { ajv.addSchema({$id: "//e.com/int.json#", type: "integer"}, "int#") ajv.validate("int", 1).should.equal(true) ajv.validate("int", "1").should.equal(false) ajv.validate("//e.com/int.json", 1).should.equal(true) ajv.validate("//e.com/int.json", "1").should.equal(false) ajv.validate("int#/", 1).should.equal(true) ajv.validate("int#/", "1").should.equal(false) ajv.validate("//e.com/int.json#/", 1).should.equal(true) ajv.validate("//e.com/int.json#/", "1").should.equal(false) }) it("should add and compile array of schemas with ids", () => { ajv.addSchema([ {$id: "//e.com/int.json", type: "integer"}, {$id: "//e.com/str.json", type: "string"}, ]) const validate0 = ajv.getSchema("//e.com/int.json") const validate1 = ajv.getSchema("//e.com/str.json") assert(typeof validate0 == "function") assert(typeof validate1 == "function") validate0(1).should.equal(true) validate0("1").should.equal(false) validate1("a").should.equal(true) validate1(1).should.equal(false) ajv.validate("//e.com/int.json", 1).should.equal(true) ajv.validate("//e.com/int.json", "1").should.equal(false) ajv.validate("//e.com/str.json", "a").should.equal(true) ajv.validate("//e.com/str.json", 1).should.equal(false) }) it("should throw on duplicate key", () => { ajv.addSchema({type: "integer"}, "int") should.throw(() => { ajv.addSchema({type: "integer", minimum: 1}, "int") }, /already exists/) }) it("should throw on duplicate normalized key", () => { ajv.addSchema({type: "number"}, "num") should.throw(() => { ajv.addSchema({type: "integer"}, "num#") }, /already exists/) should.throw(() => { ajv.addSchema({type: "integer"}, "num#/") }, /already exists/) }) it("should allow only one schema without key and id", () => { ajv.addSchema({type: "number"}) should.throw(() => { ajv.addSchema({type: "integer"}) }, /already exists/) should.throw(() => { ajv.addSchema({type: "integer"}, "") }, /already exists/) should.throw(() => { ajv.addSchema({type: "integer"}, "#") }, /already exists/) }) it("should throw if schema is not an object", () => { should.throw(() => { // @ts-expect-error ajv.addSchema("foo") }, /schema must be object or boolean/) }) it("should throw if schema id is not a string", () => { try { // @ts-expect-error ajv.addSchema({$id: 1, type: "integer"}) throw new Error("should have throw exception") } catch (e) { ;(e as Error).message.should.equal("schema $id must be string") } }) it("should return instance of itself", () => { const res = ajv.addSchema({type: "integer"}, "int") res.should.equal(ajv) }) }) describe("getSchema method", () => { it("should return compiled schema by key", () => { ajv.addSchema({type: "integer"}, "int") const validate = ajv.getSchema("int") assert(typeof validate == "function") validate(1).should.equal(true) validate("1").should.equal(false) }) it("should return compiled schema by id or ref", () => { ajv.addSchema({$id: "//e.com/int.json", type: "integer"}) const validate = ajv.getSchema("//e.com/int.json") assert(typeof validate == "function") validate(1).should.equal(true) validate("1").should.equal(false) }) it("should return compiled schema without key or with empty key", () => { ajv.addSchema({type: "integer"}) const validate = ajv.getSchema("") assert(typeof validate == "function") validate(1).should.equal(true) validate("1").should.equal(false) }) it("should return schema fragment by ref", () => { ajv.addSchema({ $id: "http://e.com/types.json", definitions: { int: {type: "integer"}, str: {type: "string"}, }, }) const vInt = ajv.getSchema("http://e.com/types.json#/definitions/int") assert(typeof vInt == "function") vInt(1).should.equal(true) vInt("1").should.equal(false) }) it("should return schema fragment by ref with protocol-relative URIs", () => { ajv.addSchema({ $id: "//e.com/types.json", definitions: { int: {type: "integer"}, str: {type: "string"}, }, }) const vInt = ajv.getSchema("//e.com/types.json#/definitions/int") assert(typeof vInt == "function") vInt(1).should.equal(true) vInt("1").should.equal(false) }) it("should return schema fragment by id", () => { ajv.addSchema({ $id: "http://e.com/types.json", definitions: { int: {$id: "#int", type: "integer"}, str: {$id: "#str", type: "string"}, }, }) const vInt = ajv.getSchema("http://e.com/types.json#int") assert(typeof vInt == "function") vInt(1).should.equal(true) vInt("1").should.equal(false) }) }) describe("removeSchema method", () => { it("should remove schema by key", () => { const schema = {type: "integer"} ajv.addSchema(schema, "int") const v = ajv.getSchema("int") assert(typeof v == "function") v.should.be.a("function") //@ts-expect-error ajv._cache.get(schema).validate.should.equal(v) ajv.removeSchema("int") should.not.exist(ajv.getSchema("int")) //@ts-expect-error should.not.exist(ajv._cache.get(schema)) }) it("should remove schema by id", () => { const schema = {$id: "//e.com/int.json", type: "integer"} ajv.addSchema(schema) const v = ajv.getSchema("//e.com/int.json") assert(typeof v == "function") v.should.be.a("function") //@ts-expect-error ajv._cache.get(schema).validate.should.equal(v) ajv.removeSchema("//e.com/int.json") should.not.exist(ajv.getSchema("//e.com/int.json")) //@ts-expect-error should.not.exist(ajv._cache.get(schema)) }) it("should remove schema by schema object", () => { const schema = {type: "integer"} ajv.addSchema(schema) //@ts-expect-error ajv._cache.get(schema).should.be.an("object") ajv.removeSchema(schema) //@ts-expect-error should.not.exist(ajv._cache.get(schema)) }) it("should remove schema with id by schema object", () => { const schema = {$id: "//e.com/int.json", type: "integer"} ajv.addSchema(schema) //@ts-expect-error ajv._cache.get(schema).should.be.an("object") ajv.removeSchema(schema) should.not.exist(ajv.getSchema("//e.com/int.json")) //@ts-expect-error should.not.exist(ajv._cache.get(schema)) }) it("should not throw if there is no schema with passed id", () => { should.not.exist(ajv.getSchema("//e.com/int.json")) should.not.throw(() => { ajv.removeSchema("//e.com/int.json") }) }) it("should remove all schemas but meta-schemas if called without an arguments", () => { const schema1 = {$id: "//e.com/int.json", type: "integer"} ajv.addSchema(schema1) //@ts-expect-error ajv._cache.get(schema1).should.be.an("object") const schema2 = {type: "integer"} ajv.addSchema(schema2) //@ts-expect-error ajv._cache.get(schema2).should.be.an("object") ajv.removeSchema() //@ts-expect-error should.not.exist(ajv._cache.get(schema1)) //@ts-expect-error should.not.exist(ajv._cache.get(schema2)) }) it("should remove all schemas but meta-schemas with key/id matching pattern", () => { const schema1 = {$id: "//e.com/int.json", type: "integer"} ajv.addSchema(schema1) //@ts-expect-error ajv._cache.get(schema1).should.be.an("object") const schema2 = {$id: "str.json", type: "string"} ajv.addSchema(schema2, "//e.com/str.json") //@ts-expect-error ajv._cache.get(schema2).should.be.an("object") const schema3 = {type: "integer"} ajv.addSchema(schema3) //@ts-expect-error ajv._cache.get(schema3).should.be.an("object") ajv.removeSchema(/e\.com/) //@ts-expect-error should.not.exist(ajv._cache.get(schema1)) //@ts-expect-error should.not.exist(ajv._cache.get(schema2)) //@ts-expect-error ajv._cache.get(schema3).should.be.an("object") }) it("should return instance of itself", () => { const res = ajv.addSchema({type: "integer"}, "int").removeSchema("int") res.should.equal(ajv) }) }) describe("addFormat method", () => { it("should add format as regular expression", () => { ajv.addFormat("identifier", /^[a-z_$][a-z0-9_$]*$/i) testFormat() }) it("should add format as string", () => { ajv.addFormat("identifier", "^[A-Za-z_$][A-Za-z0-9_$]*$") testFormat() }) it("should add format as function", () => { ajv.addFormat("identifier", (str) => /^[a-z_$][a-z0-9_$]*$/i.test(str)) testFormat() }) it("should add format as object", () => { ajv.addFormat("identifier", { validate: (str: string) => /^[a-z_$][a-z0-9_$]*$/i.test(str), }) testFormat() }) it("should return instance of itself", () => { const res = ajv.addFormat("identifier", /^[a-z_$][a-z0-9_$]*$/i) res.should.equal(ajv) }) function testFormat() { const validate = ajv.compile({ type: ["number", "string"], format: "identifier", }) validate("Abc1").should.equal(true) validate("123").should.equal(false) validate(123).should.equal(true) } describe("formats for number", () => { it("should validate only numbers", () => { ajv.addFormat("positive", { type: "number", validate: function (x: number) { return x > 0 }, }) const validate = ajv.compile({ type: ["string", "number"], format: "positive", }) validate(-2).should.equal(false) validate(0).should.equal(false) validate(2).should.equal(true) validate("abc").should.equal(true) }) it("should validate numbers with format via $data", () => { ajv = new _Ajv({$data: true, allowUnionTypes: true}) ajv.addFormat("positive", { type: "number", validate: function (x: number) { return x > 0 }, }) const validate = ajv.compile({ type: "object", properties: { data: { type: ["number", "string"], format: {$data: "1/frmt"}, }, frmt: {type: "string"}, }, }) validate({data: -2, frmt: "positive"}).should.equal(false) validate({data: 0, frmt: "positive"}).should.equal(false) validate({data: 2, frmt: "positive"}).should.equal(true) validate({data: "abc", frmt: "positive"}).should.equal(true) }) }) }) describe("validateSchema method", () => { it("should validate schema against meta-schema", () => { let valid = ajv.validateSchema({ $schema: "http://json-schema.org/draft-07/schema#", type: "number", }) valid.should.equal(true) should.equal(ajv.errors, null) valid = ajv.validateSchema({ $schema: "http://json-schema.org/draft-07/schema#", type: "wrong_type", }) valid.should.equal(false) assert(Array.isArray(ajv.errors)) ajv.errors.length.should.equal(3) ajv.errors[0].keyword.should.equal("enum") ajv.errors[1].keyword.should.equal("type") ajv.errors[2].keyword.should.equal("anyOf") }) it("should throw exception if meta-schema is unknown", () => { should.throw(() => { ajv.validateSchema({ $schema: "http://example.com/unknown/schema#", type: "number", }) }, /no schema with key or ref/) }) it("should throw exception if $schema is not a string", () => { should.throw(() => { ajv.validateSchema({ //@ts-expect-error $schema: {}, type: "number", }) }, /\$schema must be a string/) }) describe("sub-schema validation outside of definitions during compilation", () => { it("maximum", () => { passValidationThrowCompile({ $ref: "#/foo", foo: {type: "number", maximum: "bar"}, }) }) it("exclusiveMaximum", () => { passValidationThrowCompile({ $ref: "#/foo", foo: {type: "number", exclusiveMaximum: "bar"}, }) }) it("maxItems", () => { passValidationThrowCompile({ $ref: "#/foo", foo: {type: "array", maxItems: "bar"}, }) }) it("maxLength", () => { passValidationThrowCompile({ $ref: "#/foo", foo: {type: "string", maxLength: "bar"}, }) }) it("maxProperties", () => { passValidationThrowCompile({ $ref: "#/foo", foo: {type: "object", maxProperties: "bar"}, }) }) it("multipleOf", () => { passValidationThrowCompile({ $ref: "#/foo", foo: {type: "number", multipleOf: "bar"}, }) }) function passValidationThrowCompile(schema: SchemaObject) { ajv.validateSchema(schema).should.equal(true) should.throw(() => { ajv.compile(schema) }, /value must be/) } }) }) }) ================================================ FILE: spec/ajv.ts ================================================ import type Ajv from "../dist/core" const AjvClass: typeof Ajv = typeof window == "object" ? (window as any).ajv7 : require("" + "..") export default AjvClass module.exports = AjvClass module.exports.default = AjvClass ================================================ FILE: spec/ajv2019.ts ================================================ import type Ajv2019 from "../dist/2019" const AjvClass: typeof Ajv2019 = typeof window == "object" ? (window as any).ajv2019 : require("" + "../dist/2019") export default AjvClass module.exports = AjvClass module.exports.default = AjvClass ================================================ FILE: spec/ajv2020.ts ================================================ import type Ajv2020 from "../dist/2019" const AjvClass: typeof Ajv2020 = typeof window == "object" ? (window as any).ajv2020 : require("" + "../dist/2020") export default AjvClass module.exports = AjvClass module.exports.default = AjvClass ================================================ FILE: spec/ajv_all_instances.ts ================================================ import type AjvCore from "../dist/core" import type {Options} from ".." import _Ajv from "./ajv" import _Ajv2019 from "./ajv2019" import getAjvInstances from "./ajv_instances" export default function getAjvAllInstances(options: Options, extraOpts: Options = {}): AjvCore[] { return [...getAjvs(_Ajv), ...getAjvs(_Ajv2019)] function getAjvs(Ajv: typeof AjvCore): AjvCore[] { return getAjvInstances(Ajv, options, extraOpts) } } ================================================ FILE: spec/ajv_async_instances.ts ================================================ import getAjvInstances from "./ajv_instances" import _Ajv from "./ajv" import type AjvCore from "../dist/core" import type {Options} from ".." export default function getAjvSyncInstances(extraOpts?: Options): AjvCore[] { return getAjvInstances( _Ajv, { strict: false, allErrors: true, code: {lines: true, optimize: false}, }, extraOpts ) } ================================================ FILE: spec/ajv_instances.ts ================================================ import type AjvCore from "../dist/core" import type {Options} from ".." export default function getAjvInstances( _Ajv: typeof AjvCore, options: Options, extraOpts: Options = {} ): AjvCore[] { return _getAjvInstances(options, {...extraOpts, logger: false}) function _getAjvInstances(opts: Options, useOpts: Options): AjvCore[] { const optNames = Object.keys(opts) if (optNames.length) { opts = Object.assign({}, opts) const useOpts1 = Object.assign({}, useOpts) const optName = optNames[0] useOpts1[optName] = opts[optName] delete opts[optName] return [..._getAjvInstances(opts, useOpts), ..._getAjvInstances(opts, useOpts1)] } return [new _Ajv(useOpts)] } } ================================================ FILE: spec/ajv_jtd.ts ================================================ import type AjvJTD from "../dist/jtd" const AjvClass: typeof AjvJTD = typeof window == "object" ? (window as any).ajvJTD : require("" + "../dist/jtd") export default AjvClass module.exports = AjvClass module.exports.default = AjvClass ================================================ FILE: spec/ajv_options.ts ================================================ import type {Options} from ".." const isBrowser = typeof window == "object" const fullTest = !isBrowser && process.env.AJV_FULL_TEST const codeOptions = {es5: true, lines: true, optimize: false} const options: Options = fullTest ? { allErrors: true, verbose: true, inlineRefs: false, code: codeOptions, } : {allErrors: true, code: codeOptions} export default options ================================================ FILE: spec/ajv_standalone.ts ================================================ import type AjvCore from "../dist/core" import type {Options} from ".." import AjvPack from "../dist/standalone/instance" export function withStandalone(instances: AjvCore[]): (AjvCore | AjvPack)[] { return [...(instances as (AjvCore | AjvPack)[]), ...instances.map(makeStandalone)] } function makeStandalone(ajv: AjvCore): AjvPack { ajv.opts.code.source = true return new AjvPack(ajv) } export function getStandalone(_Ajv: typeof AjvCore, opts: Options = {}): AjvPack { opts.code ||= {} opts.code.source = true return new AjvPack(new _Ajv(opts)) } ================================================ FILE: spec/async/boolean.json ================================================ [ { "description": "boolean schema = true in properties", "schema": { "$async": true, "type": "object", "properties": { "foo": true } }, "tests": [ { "description": "any data is valid", "data": {"foo": 1}, "valid": true } ] }, { "description": "boolean schema = false in properties", "schema": { "$async": true, "type": "object", "properties": { "foo": false } }, "tests": [ { "description": "any property is invalid", "data": {"foo": 1}, "valid": false }, { "description": "without property is valid", "data": {"bar": 1}, "valid": true }, { "description": "empty object is valid", "data": {}, "valid": true } ] }, { "description": "boolean schema = true in $ref", "schema": { "$async": true, "$ref": "#/definitions/true", "definitions": { "true": true } }, "tests": [ { "description": "any data is valid", "data": 1, "valid": true } ] }, { "description": "boolean schema = false in $ref", "schema": { "$async": true, "$ref": "#/definitions/false", "definitions": { "false": false } }, "tests": [ { "description": "any data is invalid", "data": 1, "valid": false } ] }, { "description": "boolean schema = true in properties with $ref", "schema": { "$async": true, "type": "object", "properties": { "foo": {"$ref": "#/definitions/foo"} }, "definitions": { "foo": true } }, "tests": [ { "description": "any data is valid", "data": {"foo": 1}, "valid": true } ] }, { "description": "boolean schema = false in properties with $ref", "schema": { "$async": true, "type": "object", "properties": { "foo": {"$ref": "#/definitions/foo"} }, "definitions": { "foo": false } }, "tests": [ { "description": "any property is invalid", "data": {"foo": 1}, "valid": false }, { "description": "without property is valid", "data": {"bar": 1}, "valid": true }, { "description": "empty object is valid", "data": {}, "valid": true } ] } ] ================================================ FILE: spec/async/compound.json ================================================ [ { "description": "allOf: async + sync", "schema": { "$async": true, "type": "integer", "allOf": [ { "idExists": {"table": "users"} }, { "minimum": 3 } ] }, "tests": [ { "description": "valid id", "data": 5, "valid": true }, { "description": "another valid id", "data": 8, "valid": true }, { "description": "invalid async - not user id", "data": 9, "valid": false }, { "description": "invalid sync - valid id but too small", "data": 1, "valid": false } ] }, { "description": "anyOf: async + sync", "schema": { "$async": true, "type": "integer", "anyOf": [ { "idExists": {"table": "users"} }, { "minimum": 3 } ] }, "tests": [ { "description": "valid id", "data": 1, "valid": true }, { "description": "valid - not id but big enough", "data": 4, "valid": true }, { "description": "valid - id and big enough", "data": 5, "valid": true }, { "description": "invalid both", "data": 2, "valid": false } ] }, { "description": "oneOf: async + sync", "schema": { "$async": true, "type": "integer", "oneOf": [ { "idExists": {"table": "users"} }, { "minimum": 3 } ] }, "tests": [ { "description": "valid id", "data": 1, "valid": true }, { "description": "valid - not id but big enough", "data": 4, "valid": true }, { "description": "invalid - id and big enough", "data": 5, "valid": false }, { "description": "invalid both", "data": 2, "valid": false } ] }, { "description": "not with async", "schema": { "$async": true, "type": "integer", "not": { "idExists": {"table": "users"} } }, "tests": [ { "description": "invalid because valid id", "data": 1, "valid": false }, { "description": "valid because not a valid id", "data": 4, "valid": true } ] } ] ================================================ FILE: spec/async/format.json ================================================ [ { "description": "async user-defined formats", "schema": { "$async": true, "type": "string", "format": "english_word" }, "tests": [ { "description": "'tomorrow' is a valid english word", "data": "tomorrow", "valid": true }, { "description": "'manana' is an invalid english word", "data": "manana", "valid": false }, { "description": "number is invalid", "data": 1, "valid": false }, { "description": "'today' throws an exception, not in the dictionary", "data": "today", "error": "unknown word" } ] }, { "description": "async formats when $data ref resolves to async format name", "schema": { "$async": true, "type": "object", "additionalProperties": { "type": "string", "format": {"$data": "0#"} } }, "tests": [ { "description": "'tomorrow' is a valid english word", "data": {"english_word": "tomorrow"}, "valid": true }, { "description": "'manana' is an invalid english word", "data": {"english_word": "manana"}, "valid": false }, { "description": "number is invalid", "data": {"english_word": 1}, "valid": false }, { "description": "'today' throws an exception, not in the dictionary", "data": {"english_word": "today"}, "error": "unknown word" }, { "description": "valid date", "data": {"date": "2016-01-25"}, "valid": true }, { "description": "invalid date", "data": {"date": "01/25/2016"}, "valid": false }, { "description": "number is invalid", "data": {"date": 1}, "valid": false } ] } ] ================================================ FILE: spec/async/items.json ================================================ [ { "description": "items: async + sync", "schema": { "$async": true, "type": "array", "items": [ { "type": "integer", "idExists": {"table": "users"} }, { "type": "integer" }, { "type": "integer", "idExists": {"table": "users"} } ], "minItems": 3, "additionalItems": false }, "tests": [ { "description": "valid array", "data": [1, 2, 5], "valid": true }, { "description": "another valid array", "data": [5, 2, 8], "valid": true }, { "description": "invalid 1st async item", "data": [9, 2, 8], "valid": false }, { "description": "invalid 2nd async item", "data": [1, 2, 9], "valid": false }, { "description": "invalid sync item", "data": [1, "abc", 5], "valid": false } ] } ] ================================================ FILE: spec/async/keyword.json ================================================ [ { "description": "async keywords (validated)", "schema": { "$async": true, "type": "object", "properties": { "userId": { "type": "integer", "idExists": {"table": "users"} }, "postId": { "type": "integer", "idExists": {"table": "posts"} }, "categoryId": { "description": "will throw if present, no such table", "type": "integer", "idExists": {"table": "categories"} } } }, "tests": [ { "description": "valid object", "data": {"userId": 1, "postId": 21}, "valid": true }, { "description": "another valid object", "data": {"userId": 5, "postId": 25}, "valid": true }, { "description": "invalid - no such post", "data": {"userId": 5, "postId": 10}, "valid": false }, { "description": "invalid - no such user", "data": {"userId": 9, "postId": 25}, "valid": false }, { "description": "should throw exception during validation - no such table", "data": {"postId": 25, "categoryId": 1}, "error": "no such table" } ] }, { "description": "async user-defined keywords (validated with errors)", "schema": { "$async": true, "type": "object", "properties": { "userId": { "type": "integer", "idExistsWithError": {"table": "users"} }, "postId": { "type": "integer", "idExistsWithError": {"table": "posts"} }, "categoryId": { "description": "will throw if present, no such table", "type": "integer", "idExistsWithError": {"table": "categories"} } } }, "tests": [ { "description": "valid object", "data": {"userId": 1, "postId": 21}, "valid": true }, { "description": "another valid object", "data": {"userId": 5, "postId": 25}, "valid": true }, { "description": "invalid - no such post", "data": {"userId": 5, "postId": 10}, "valid": false }, { "description": "invalid - no such user", "data": {"userId": 9, "postId": 25}, "valid": false }, { "description": "should throw exception during validation - no such table", "data": {"postId": 25, "categoryId": 1}, "error": "no such table" } ] }, { "description": "async user-defined keywords (compiled)", "schema": { "$async": true, "type": "object", "properties": { "userId": { "type": "integer", "idExistsCompiled": {"table": "users"} }, "postId": { "type": "integer", "idExistsCompiled": {"table": "posts"} } } }, "tests": [ { "description": "valid object", "data": {"userId": 1, "postId": 21}, "valid": true }, { "description": "another valid object", "data": {"userId": 5, "postId": 25}, "valid": true }, { "description": "invalid - no such post", "data": {"userId": 5, "postId": 10}, "valid": false }, { "description": "invalid - no such user", "data": {"userId": 9, "postId": 25}, "valid": false } ] }, { "description": "keyword in async schema", "schema": { "$async": true, "const": 5 }, "tests": [ { "description": "valid", "data": 5, "valid": true }, { "description": "valid", "data": 1, "valid": false } ] } ] ================================================ FILE: spec/async/no_async.json ================================================ [ { "description": "async schema without async elements", "schema": { "$async": true, "type": "string", "maxLength": 3 }, "tests": [ { "description": "string <= 3 chars is valid", "data": "abc", "valid": true }, { "description": "string > 3 chars is invalid", "data": "abcd", "valid": false }, { "description": "number is invalid", "data": 1, "valid": false } ] } ] ================================================ FILE: spec/async/properties.json ================================================ [ { "description": "properties: async + sync", "schema": { "$async": true, "type": "object", "properties": { "foo": { "type": "integer", "idExists": {"table": "users"} }, "bar": { "type": "integer" } } }, "tests": [ { "description": "valid object", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "another valid object", "data": {"foo": 5, "bar": 2}, "valid": true }, { "description": "invalid sync property", "data": {"foo": 1, "bar": "abc"}, "valid": false }, { "description": "invalid async property", "data": {"foo": 9, "bar": 2}, "valid": false } ] } ] ================================================ FILE: spec/async.spec.ts ================================================ import _Ajv from "./ajv" import type {SchemaObject, AnyValidateFunction} from "../dist/types" import chai from "./chai" const should = chai.should() describe("compileAsync method", () => { let ajv, loadCallCount const SCHEMAS = { "http://example.com/object.json": { $id: "http://example.com/object.json", type: "object", properties: { a: {type: "string"}, b: {$ref: "int2plus.json"}, }, }, "http://example.com/int2plus.json": { $id: "http://example.com/int2plus.json", type: "integer", minimum: 2, }, "http://example.com/tree.json": { $id: "http://example.com/tree.json", type: "array", items: {$ref: "leaf.json"}, }, "http://example.com/leaf.json": { $id: "http://example.com/leaf.json", type: "object", properties: { name: {type: "string"}, subtree: {$ref: "tree.json"}, }, }, "http://example.com/recursive.json": { $id: "http://example.com/recursive.json", type: "object", properties: { b: {$ref: "parent.json"}, }, required: ["b"], }, "http://example.com/invalid.json": { $id: "http://example.com/recursive.json", type: "object", properties: { invalid: {type: "number"}, }, required: "invalid", }, "http://example.com/foobar.json": { $id: "http://example.com/foobar.json", $schema: "http://example.com/foobar_meta.json", type: "string", myFooBar: "foo", }, "http://example.com/foobar_meta.json": { $id: "http://example.com/foobar_meta.json", type: "object", properties: { myFooBar: { enum: ["foo", "bar"], }, }, }, "http://example.com/foo.json": { $id: "http://example.com/foo.json", type: "object", properties: { bar: {$ref: "bar.json"}, other: {$ref: "other.json"}, }, }, "http://example.com/bar.json": { $id: "http://example.com/bar.json", type: "object", properties: { foo: {$ref: "foo.json"}, }, }, "http://example.com/other.json": { $id: "http://example.com/other.json", }, } beforeEach(() => { loadCallCount = 0 ajv = new _Ajv({loadSchema}) }) it("should compile schemas loading missing schemas with options.loadSchema function", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "object.json"}, }, } return ajv.compileAsync(schema).then((validate) => { should.equal(loadCallCount, 2) validate.should.be.a("function") validate({a: {b: 2}}).should.equal(true) validate({a: {b: 1}}).should.equal(false) }) }) it("should compile schemas loading missing schemas and return promise with function", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "object.json"}, }, } return ajv.compileAsync(schema).then((validate) => { should.equal(loadCallCount, 2) validate.should.be.a("function") validate({a: {b: 2}}).should.equal(true) validate({a: {b: 1}}).should.equal(false) }) }) it("should correctly load schemas when missing reference has JSON path", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "object.json#/properties/b"}, }, } return ajv.compileAsync(schema).then((validate) => { should.equal(loadCallCount, 2) validate.should.be.a("function") validate({a: 2}).should.equal(true) validate({a: 1}).should.equal(false) }) }) it("should correctly compile with remote schemas that have mutual references", () => { const schema = { $id: "http://example.com/root.json", type: "object", properties: { tree: {$ref: "tree.json"}, }, } return ajv.compileAsync(schema).then((validate) => { validate.should.be.a("function") const validData = { tree: [{name: "a", subtree: [{name: "a.a"}]}, {name: "b"}], } const invalidData = {tree: [{name: "a", subtree: [{name: 1}]}]} validate(validData).should.equal(true) validate(invalidData).should.equal(false) }) }) it("should correctly compile with remote schemas that reference the compiled schema", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "recursive.json"}, }, } return ajv.compileAsync(schema).then((validate) => { should.equal(loadCallCount, 1) validate.should.be.a("function") const validData = {a: {b: {a: {b: {}}}}} const invalidData = {a: {b: {a: {}}}} validate(validData).should.equal(true) validate(invalidData).should.equal(false) }) }) it('should resolve reference containing "properties" segment with the same property (issue #220)', () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: { $ref: "object.json#/properties/a", }, }, } return ajv.compileAsync(schema).then((validate) => { should.equal(loadCallCount, 2) validate.should.be.a("function") validate({a: "foo"}).should.equal(true) validate({a: 42}).should.equal(false) }) }) describe("loading metaschemas (#334)", () => { it("should load metaschema if not available", () => { return test(SCHEMAS["http://example.com/foobar.json"], 1) }) it("should load metaschema of referenced schema if not available", () => { return test({$ref: "http://example.com/foobar.json"}, 2) }) function test(schema, expectedLoadCallCount) { ajv.addKeyword({ keyword: "myFooBar", type: "string", validate: function (sch, data) { return sch === data }, }) return ajv.compileAsync(schema).then((validate) => { should.equal(loadCallCount, expectedLoadCallCount) validate.should.be.a("function") validate("foo").should.equal(true) validate("bar").should.equal(false) }) } }) it("should return compiled schema on the next tick if there are no references (#51)", () => { const schema = { $id: "http://example.com/int2plus.json", type: "integer", minimum: 2, } let beforeCallback1: any = false const p1 = ajv.compileAsync(schema).then((validate) => { beforeCallback1.should.equal(true) spec(validate) let beforeCallback2: any = false const p2 = ajv.compileAsync(schema).then((_validate) => { beforeCallback2.should.equal(true) spec(_validate) }) beforeCallback2 = true return p2 }) beforeCallback1 = true return p1 function spec(validate) { should.equal(loadCallCount, 0) validate.should.be.a("function") const validData = 2 const invalidData = 1 validate(validData).should.equal(true) validate(invalidData).should.equal(false) } }) it("should queue calls so only one compileAsync executes at a time (#52)", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "object.json"}, }, } return Promise.all([ ajv.compileAsync(schema).then(spec), ajv.compileAsync(schema).then(spec), ajv.compileAsync(schema).then(spec), ]) function spec(validate) { should.equal(loadCallCount, 2) validate.should.be.a("function") validate({a: {b: 2}}).should.equal(true) validate({a: {b: 1}}).should.equal(false) } }) it("should throw exception if loadSchema is not passed", () => { const schema = { $id: "http://example.com/int2plus.json", type: "integer", minimum: 2, } ajv = new _Ajv() should.throw(() => { ajv.compileAsync(schema) }, "options.loadSchema should be a function") }) describe("should return error via promise", () => { it("if passed schema is invalid", () => { const invalidSchema = { $id: "http://example.com/int2plus.json", type: "integer", minimum: "invalid", } return shouldReject(ajv.compileAsync(invalidSchema), /schema is invalid/) }) it("if loaded schema is invalid", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "invalid.json"}, }, } return shouldReject(ajv.compileAsync(schema), /schema is invalid/) }) it("if required schema is loaded but the reference cannot be resolved", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "object.json#/definitions/not_found"}, }, } return shouldReject(ajv.compileAsync(schema), /is loaded but/) }) it("if loadSchema returned error", () => { const schema = { $id: "http://example.com/parent.json", type: "object", properties: { a: {$ref: "object.json"}, }, } ajv = new _Ajv({loadSchema: badLoadSchema}) return shouldReject(ajv.compileAsync(schema), /cant load/) function badLoadSchema() { return Promise.reject(new Error("cant load")) } }) it("if schema compilation throws some other exception", () => { ajv.addKeyword({keyword: "badkeyword", compile: badCompile}) const schema = {badkeyword: true} return shouldReject(ajv.compileAsync(schema), /cant compile keyword schema/) function badCompile(/* schema */) { throw new Error("cant compile keyword schema") } }) function shouldReject(p: Promise, rx: RegExp) { return p.then( (validate) => { should.not.exist(validate) throw new Error("Promise has resolved; it should have rejected") }, (err) => { should.exist(err) err.message.should.match(rx) } ) } }) describe("schema with multiple remote properties, the first is recursive schema (#801)", () => { it("should validate data", () => { const schema = { $id: "http://example.com/list.json", type: "object", properties: { foo: {$ref: "foo.json"}, }, } return ajv.compileAsync(schema).then((validate) => { validate({foo: {}}).should.equal(true) }) }) }) function loadSchema(uri: string): Promise { loadCallCount++ return new Promise((resolve, reject) => { setTimeout(() => { if (SCHEMAS[uri]) resolve(SCHEMAS[uri]) else reject(new Error("404")) }, 10) }) } }) ================================================ FILE: spec/async_schemas.spec.ts ================================================ import getAjvAsyncInstances from "./ajv_async_instances" import jsonSchemaTest = require("json-schema-test") import {afterError} from "./after_test" import type Ajv from ".." import _Ajv from "./ajv" import chai from "./chai" const instances = getAjvAsyncInstances({$data: true}) instances.forEach(addAsyncFormatsAndKeywords) jsonSchemaTest(instances, { description: "asynchronous schemas tests of " + instances.length + " ajv instances with different options", suites: {"async schemas": require("./_json/async")}, async: true, asyncValid: "data", assert: chai.assert, afterError, // afterEach: after.each, cwd: __dirname, hideFolder: "async/", timeout: 10000, }) function addAsyncFormatsAndKeywords(ajv: Ajv) { ajv.addFormat("date", /^\d\d\d\d-[0-1]\d-[0-3]\d$/) ajv.addFormat("english_word", { async: true, validate: checkWordOnServer, }) ajv.addKeyword({ keyword: "idExists", async: true, type: "number", validate: checkIdExists, errors: false, }) ajv.addKeyword({ keyword: "idExistsWithError", async: true, type: "number", validate: checkIdExistsWithError, errors: true, }) ajv.addKeyword({ keyword: "idExistsCompiled", async: true, type: "number", compile: compileCheckIdExists, }) } function checkWordOnServer(str: string): Promise { return str === "tomorrow" ? Promise.resolve(true) : str === "manana" ? Promise.resolve(false) : Promise.reject(new Error("unknown word")) } function checkIdExists(schema: {table: string}, data: number): Promise { switch (schema.table) { case "users": return check([1, 5, 8]) case "posts": return check([21, 25, 28]) default: throw new Error("no such table") } function check(IDs: number[]): Promise { return Promise.resolve(IDs.includes(data)) } } function checkIdExistsWithError(schema: {table: string}, data: number): Promise { const {table} = schema switch (table) { case "users": return check(table, [1, 5, 8]) case "posts": return check(table, [21, 25, 28]) default: throw new Error("no such table") } function check(_table: string, IDs: number[]): Promise { if (IDs.includes(data)) return Promise.resolve(true) const error = { keyword: "idExistsWithError", message: "id not found in table " + _table, } return Promise.reject(new _Ajv.ValidationError([error])) } } function compileCheckIdExists(schema: {table: string}): (data: number) => Promise { switch (schema.table) { case "users": return compileCheck([1, 5, 8]) case "posts": return compileCheck([21, 25, 28]) default: throw new Error("no such table") } function compileCheck(IDs: number[]): (data: number) => Promise { return (data) => Promise.resolve(IDs.includes(data)) } } ================================================ FILE: spec/async_validate.spec.ts ================================================ import getAjvAsyncInstances from "./ajv_async_instances" import _Ajv from "./ajv" import chai from "./chai" const should = chai.should() describe("async schemas, formats and keywords", function () { this.timeout(30000) let ajv, instances beforeEach(() => { instances = getAjvAsyncInstances() ajv = instances[0] }) describe("async schemas without async elements", () => { it("should return result as promise", () => { const schema = { $async: true, type: "string", maxLength: 3, } return repeat(() => { return Promise.all(instances.map(test)) }) function test(_ajv) { const validate = _ajv.compile(schema) return Promise.all([ shouldBeValid(validate("abc"), "abc"), shouldBeInvalid(validate("abcd")), shouldBeInvalid(validate(1)), ]) } }) it("should fail compilation if async schema is inside sync schema", () => { const schema: any = { type: "object", properties: { foo: { $async: true, type: "string", maxLength: 3, }, }, } should.throw(() => { ajv.compile(schema) }, "async schema in sync schema") ajv.compile({...schema, $async: true}) }) }) describe("async formats", () => { beforeEach(addFormatEnglishWord) it("should fail compilation if async format is inside sync schema", () => { instances.forEach((_ajv) => { let schema: any = { type: "string", format: "english_word", } should.throw(() => { _ajv.compile(schema) }, "async format in sync schema") schema = {...schema, $async: true} _ajv.compile(schema) }) }) }) describe("async user-defined keywords", () => { beforeEach(() => { instances.forEach((_ajv) => { _ajv.addKeyword({ keyword: "idExists", async: true, type: "number", validate: checkIdExists, errors: false, }) _ajv.addKeyword({ keyword: "idExistsWithError", async: true, type: "number", validate: checkIdExistsWithError, errors: true, }) }) }) it("should fail compilation if async keyword is inside sync schema", () => { instances.forEach((_ajv) => { let schema: any = { type: "object", properties: { userId: { type: "integer", idExists: {table: "users"}, }, }, } should.throw(() => { _ajv.compile(schema) }, "async keyword in sync schema") schema = {...schema, $async: true} _ajv.compile(schema) }) }) it("should return user-defined error", () => { return Promise.all( instances.map((_ajv) => { const schema = { $async: true, type: "object", properties: { userId: { type: "integer", idExistsWithError: {table: "users"}, }, postId: { type: "integer", idExistsWithError: {table: "posts"}, }, }, } const validate = _ajv.compile(schema) return Promise.all([ shouldBeInvalid(validate({userId: 5, postId: 10}), ["id not found in table posts"]), shouldBeInvalid(validate({userId: 9, postId: 25}), ["id not found in table users"]), ]) }) ) }) function checkIdExists(schema, data) { switch (schema.table) { case "users": return check([1, 5, 8]) case "posts": return check([21, 25, 28]) default: throw new Error("no such table") } function check(IDs) { return Promise.resolve(IDs.indexOf(data) >= 0) } } function checkIdExistsWithError(schema, data) { const {table} = schema switch (table) { case "users": return check(table, [1, 5, 8]) case "posts": return check(table, [21, 25, 28]) default: throw new Error("no such table") } function check(_table, IDs) { if (IDs.indexOf(data) >= 0) return Promise.resolve(true) const error = { keyword: "idExistsWithError", message: "id not found in table " + _table, } return Promise.reject(new _Ajv.ValidationError([error])) } } }) describe("async referenced schemas", () => { beforeEach(() => { instances = getAjvAsyncInstances({inlineRefs: false, ignoreKeywordsWithRef: true}) addFormatEnglishWord() }) it("should validate referenced async schema", () => { const schema = { $async: true, definitions: { english_word: { $async: true, type: "string", format: "english_word", }, }, type: "object", properties: { word: {$ref: "#/definitions/english_word"}, }, } return repeat(() => { return Promise.all( instances.map((_ajv) => { const validate = _ajv.compile(schema) const validData = {word: "tomorrow"} return Promise.all([ shouldBeValid(validate(validData), validData), shouldBeInvalid(validate({word: "manana"})), shouldBeInvalid(validate({word: 1})), shouldThrow(validate({word: "today"}), "unknown word"), ]) }) ) }) }) it("should validate recursive async schema", () => { const schema = { $async: true, definitions: { english_word: { $async: true, type: "string", format: "english_word", }, }, type: "object", properties: { foo: { anyOf: [{$ref: "#/definitions/english_word"}, {$ref: "#"}], }, }, } return recursiveTest(schema) }) it("should validate recursive ref to async sub-schema, issue #612", () => { const schema = { $async: true, type: "object", properties: { foo: { $async: true, anyOf: [ { type: "string", format: "english_word", }, { type: "object", properties: { foo: {$ref: "#/properties/foo"}, }, }, ], }, }, } return recursiveTest(schema) }) it("should validate ref from referenced async schema to root schema", () => { const schema = { $async: true, definitions: { wordOrRoot: { $async: true, anyOf: [ { type: "string", format: "english_word", }, {$ref: "#"}, ], }, }, type: "object", properties: { foo: {$ref: "#/definitions/wordOrRoot"}, }, } return recursiveTest(schema) }) it("should validate refs between two async schemas", () => { const schemaObj = { $id: "http://e.com/obj.json#", $async: true, type: "object", properties: { foo: {$ref: "http://e.com/word.json#"}, }, } const schemaWord = { $id: "http://e.com/word.json#", $async: true, anyOf: [ { type: "string", format: "english_word", }, {$ref: "http://e.com/obj.json#"}, ], } return recursiveTest(schemaObj, schemaWord) }) it("should fail compilation if sync schema references async schema", () => { let schema: any = { $id: "http://e.com/obj.json#", type: "object", properties: { foo: {$ref: "http://e.com/word.json#"}, }, } const schemaWord = { $id: "http://e.com/word.json#", $async: true, anyOf: [ { type: "string", format: "english_word", }, {$ref: "http://e.com/obj.json#"}, ], } ajv.addSchema(schemaWord) ajv.addFormat("english_word", { async: true, validate: checkWordOnServer, }) should.throw(() => { ajv.compile(schema) }, "async schema referenced by sync schema") schema = {...schema, $id: "http://e.com/obj2.json#", $async: true} ajv.compile(schema) }) function recursiveTest(schema, refSchema?) { return repeat(() => { return Promise.all( instances.map((_ajv) => { if (refSchema) _ajv.addSchema(refSchema) const validate = _ajv.compile(schema) let data return Promise.all([ shouldBeValid(validate((data = {foo: "tomorrow"})), data), shouldBeInvalid(validate({foo: "manana"})), shouldBeInvalid(validate({foo: 1})), shouldThrow(validate({foo: "today"}), "unknown word"), shouldBeValid(validate((data = {foo: {foo: "tomorrow"}})), data), shouldBeInvalid(validate({foo: {foo: "manana"}})), shouldBeInvalid(validate({foo: {foo: 1}})), shouldThrow(validate({foo: {foo: "today"}}), "unknown word"), shouldBeValid(validate((data = {foo: {foo: {foo: "tomorrow"}}})), data), shouldBeInvalid(validate({foo: {foo: {foo: "manana"}}})), shouldBeInvalid(validate({foo: {foo: {foo: 1}}})), shouldThrow(validate({foo: {foo: {foo: "today"}}}), "unknown word"), ]) }) ) }) } }) function addFormatEnglishWord() { instances.forEach((_ajv) => { _ajv.addFormat("english_word", { async: true, validate: checkWordOnServer, }) }) } }) function checkWordOnServer(str) { return str === "tomorrow" ? Promise.resolve(true) : str === "manana" ? Promise.resolve(false) : Promise.reject(new Error("unknown word")) } function shouldBeValid(p, data) { return p.then((valid) => valid.should.equal(data)) } const SHOULD_BE_INVALID = "test: should be invalid" function shouldBeInvalid(p, expectedMessages?: string[]) { return checkNotValid(p).then((err) => { err.should.be.instanceof(_Ajv.ValidationError) err.errors.should.be.an("array") err.validation.should.equal(true) if (expectedMessages) { const messages = err.errors.map((e) => e.message) messages.should.eql(expectedMessages) } }) } function shouldThrow(p, exception) { return checkNotValid(p).then((err) => err.message.should.equal(exception)) } function checkNotValid(p) { return p .then((/* valid */) => { throw new Error(SHOULD_BE_INVALID) }) .catch((err) => { err.should.be.instanceof(Error) if (err.message === SHOULD_BE_INVALID) throw err return err }) } function repeat(func) { return func() // var promises = []; // for (var i=0; i<1000; i++) promises.push(func()); // return Promise.all(promises); } ================================================ FILE: spec/boolean.spec.ts ================================================ import _Ajv from "./ajv" import type Ajv from ".." import chai from "./chai" chai.should() describe("boolean schemas", () => { let ajvs: Ajv[] before(() => { ajvs = [ new _Ajv({strictTuples: false}), new _Ajv({allErrors: true, strictTuples: false}), new _Ajv({inlineRefs: false, strictTuples: false}), new _Ajv({strict: false}), ] }) describe("top level schema", () => { describe("schema = true", () => { it("should validate any data as valid", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should validate any data as invalid", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const validate = ajv.compile(boolSchema) testSchema(validate, valid) } } }) describe("in properties / sub-properties", () => { describe("schema = true", () => { it("should be valid with any property value", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any property value", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { type: "object", properties: { foo: boolSchema, bar: { type: "object", properties: { baz: boolSchema, }, }, }, } const validate = ajv.compile(schema) validate({foo: 1, bar: {baz: 1}}).should.equal(valid) validate({foo: "1", bar: {baz: "1"}}).should.equal(valid) validate({foo: {}, bar: {baz: {}}}).should.equal(valid) validate({foo: [], bar: {baz: []}}).should.equal(valid) validate({foo: true, bar: {baz: true}}).should.equal(valid) validate({foo: false, bar: {baz: false}}).should.equal(valid) validate({foo: null, bar: {baz: null}}).should.equal(valid) validate({bar: {quux: 1}}).should.equal(true) } } }) describe("in items / sub-items", () => { describe("schema = true", () => { it("should be valid with any item value", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any item value", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { let schema = { type: "array", items: boolSchema, } let validate = ajv.compile(schema) validate([1]).should.equal(valid) validate(["1"]).should.equal(valid) validate([{}]).should.equal(valid) validate([[]]).should.equal(valid) validate([true]).should.equal(valid) validate([false]).should.equal(valid) validate([null]).should.equal(valid) validate([]).should.equal(true) schema = { type: "array", items: [ true, { type: "array", items: [true, boolSchema], }, boolSchema, ], } validate = ajv.compile(schema) validate([1, [1, 1], 1]).should.equal(valid) validate(["1", ["1", "1"], "1"]).should.equal(valid) validate([{}, [{}, {}], {}]).should.equal(valid) validate([[], [[], []], []]).should.equal(valid) validate([true, [true, true], true]).should.equal(valid) validate([false, [false, false], false]).should.equal(valid) validate([null, [null, null], null]).should.equal(valid) validate([1, [1]]).should.equal(true) } } }) describe("in dependencies and sub-dependencies", () => { describe("schema = true", () => { it("should be valid with any property value", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any property value", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { type: "object", dependencies: { foo: boolSchema, bar: { type: "object", dependencies: { baz: boolSchema, }, }, }, } const validate = ajv.compile(schema) validate({foo: 1, bar: 1, baz: 1}).should.equal(valid) validate({foo: "1", bar: "1", baz: "1"}).should.equal(valid) validate({foo: {}, bar: {}, baz: {}}).should.equal(valid) validate({foo: [], bar: [], baz: []}).should.equal(valid) validate({foo: true, bar: true, baz: true}).should.equal(valid) validate({foo: false, bar: false, baz: false}).should.equal(valid) validate({foo: null, bar: null, baz: null}).should.equal(valid) validate({bar: 1, quux: 1}).should.equal(true) } } }) describe("in patternProperties", () => { describe("schema = true", () => { it("should be valid with any property matching pattern", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any property matching pattern", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { type: "object", patternProperties: { "^f": boolSchema, r$: { type: "object", patternProperties: { z$: boolSchema, }, }, }, } const validate = ajv.compile(schema) validate({foo: 1, bar: {baz: 1}}).should.equal(valid) validate({foo: "1", bar: {baz: "1"}}).should.equal(valid) validate({foo: {}, bar: {baz: {}}}).should.equal(valid) validate({foo: [], bar: {baz: []}}).should.equal(valid) validate({foo: true, bar: {baz: true}}).should.equal(valid) validate({foo: false, bar: {baz: false}}).should.equal(valid) validate({foo: null, bar: {baz: null}}).should.equal(valid) validate({bar: {quux: 1}}).should.equal(true) } } }) describe("in propertyNames", () => { describe("schema = true", () => { it("should be valid with any property", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any property", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { type: "object", propertyNames: boolSchema, } const validate = ajv.compile(schema) validate({foo: 1}).should.equal(valid) validate({bar: 1}).should.equal(valid) validate({}).should.equal(true) } } }) describe("in contains", () => { describe("schema = true", () => { it("should be valid with any items", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any items", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { type: "array", contains: boolSchema, } const validate = ajv.compile(schema) validate([1]).should.equal(valid) validate(["foo"]).should.equal(valid) validate([{}]).should.equal(valid) validate([[]]).should.equal(valid) validate([true]).should.equal(valid) validate([false]).should.equal(valid) validate([null]).should.equal(valid) validate([]).should.equal(false) } } }) describe("in not", () => { describe("schema = true", () => { it("should be invalid with any data", () => { ajvs.forEach(test(true, false)) }) }) describe("schema = false", () => { it("should be valid with any data", () => { ajvs.forEach(test(false, true)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { not: boolSchema, } const validate = ajv.compile(schema) testSchema(validate, valid) } } }) describe("in allOf", () => { describe("schema = true", () => { it("should be valid with any data", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any data", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { let schema = { allOf: [false, boolSchema], } let validate = ajv.compile(schema) testSchema(validate, false) schema = { allOf: [true, boolSchema], } validate = ajv.compile(schema) testSchema(validate, valid) } } }) describe("in anyOf", () => { describe("schema = true", () => { it("should be valid with any data", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any data", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { let schema = { anyOf: [false, boolSchema], } let validate = ajv.compile(schema) testSchema(validate, valid) schema = { anyOf: [true, boolSchema], } validate = ajv.compile(schema) testSchema(validate, true) } } }) describe("in oneOf", () => { describe("schema = true", () => { it("should be valid with any data", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any data", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { let schema = { oneOf: [false, boolSchema], } let validate = ajv.compile(schema) testSchema(validate, valid) schema = { oneOf: [true, boolSchema], } validate = ajv.compile(schema) testSchema(validate, !valid) } } }) describe("in $ref", () => { describe("schema = true", () => { it("should be valid with any data", () => { ajvs.forEach(test(true, true)) }) }) describe("schema = false", () => { it("should be invalid with any data", () => { ajvs.forEach(test(false, false)) }) }) function test(boolSchema, valid) { return function (ajv) { const schema = { $ref: "#/definitions/bool", definitions: { bool: boolSchema, }, } const validate = ajv.compile(schema) testSchema(validate, valid) } } }) function testSchema(validate, valid) { validate(1).should.equal(valid) validate("foo").should.equal(valid) validate({}).should.equal(valid) validate([]).should.equal(valid) validate(true).should.equal(valid) validate(false).should.equal(valid) validate(null).should.equal(valid) } }) ================================================ FILE: spec/chai.ts ================================================ import type {ChaiStatic} from "./chai_type" const chai: ChaiStatic = typeof window == "object" ? (window as any).chai : require("" + "chai") export default chai ================================================ FILE: spec/chai_type.ts ================================================ import "chai" export type ChaiStatic = Chai.ChaiStatic ================================================ FILE: spec/codegen.spec.ts ================================================ import { CodeGen, CodeGenOptions, ScopeStore, ValueScope, _, str, nil, not, Code, Name, } from "../dist/compile/codegen" import assert = require("assert") describe("code generation", () => { describe("Name", () => { it("throws if non-identifier is passed", () => { assert.throws(() => new Name("1x"), /name must be a valid identifier/) assert.throws(() => new Name("-x"), /name must be a valid identifier/) new Name("x") }) it("returns false from emptyStr", () => { assert.strictEqual(new Name("x").emptyStr(), false) }) }) describe("emptyStr", () => { it("checks empty string", () => { assert.strictEqual(nil.toString(), "") assert.strictEqual(nil.emptyStr(), true) assert.strictEqual(_`""`.emptyStr(), true) assert.strictEqual(_`"foo"`.emptyStr(), false) }) }) describe("_ tagged template", () => { it("quotes strings", () => { const x = new Name("x") const s = "foo" const code = _`${x} = ${s}` assertEqual(code, 'x = "foo"') }) it("interpolates Code, numbers, booleans and nulls without quotes", () => { const x: Name = new Name("x") const expr: Code = _`${true} ? ${1} : ${2}` const code: Code = _`${x} = ${expr}; x = ${null}` assertEqual(code, "x = true ? 1 : 2; x = null") }) }) describe("str tagged template", () => { it("quotes plain strings", () => { const code: Code = str`foo` assertEqual(code, '"foo"') }) it("merges strings", () => { const x = "-" assertEqual(str`${x}foo${x}bar${x}`, '"-foo-bar-"') }) it("creates string expressions with Code", () => { const x = new Name("x") assertEqual(str`${x}foo${x}bar${x}`, 'x+"foo"+x+"bar"+x') assertEqual(str`foo${x}${x}bar${x}`, '"foo"+x+x+"bar"+x') }) it("connects string expressions removing unnecessary additions", () => { const x = _`"foo" + ${new Name("x")} + "bar"` assertEqual(str`start ${x} end`, '"start foo" + x + "bar end"') }) it("connects strings with numbers, booleans and nulls removing unnecessary additions", () => { assertEqual(str`foo ${1} ${true} ${null} bar`, '"foo 1 true null bar"') }) it("preserves code", () => { const data = new Name("data") const code = _`${data}.replace(/~/g, "~0")` assertEqual(str`/${code}`, '"/"+data.replace(/~/g, "~0")') assertEqual(str`/${code}/`, '"/"+data.replace(/~/g, "~0")+"/"') }) }) describe("CodeGen", () => { let gen: CodeGen beforeEach(() => { gen = getGen() }) describe("name declarations", () => { it("declares const", () => { const x = gen.const("x", 1) assert(x instanceof Name) assertEqual(gen, "const x0 = 1;") }) it("declares and assigns let", () => { gen.let("x", 1) assertEqual(gen, "let x0 = 1;") }) it("declares let", () => { gen.let("x") assertEqual(gen, "let x0;") }) it("declares and assigns var", () => { gen.var("x", 1) assertEqual(gen, "var x0 = 1;") }) it("adds code", () => { const x = "hello" gen.code(_`console.log(${x})`) gen.code(() => gen.code(_`console.log(${2})`)) assertEqual(gen, 'console.log("hello");console.log(2);') }) it("returns code for object literal", () => { const bar = new Name("bar") const code = gen.object( [new Name("foo"), 1], [bar, bar], [new Name("baz"), str`hello`], [new Name("bool"), true] ) assertEqual(code, '{foo:1,bar,baz:"hello",bool:true}') }) }) describe("`if` statement", () => { const x = new Name("x") const num = 0 it("renders if/else if/else clauses", () => { gen.if(_`${x} > ${num}`) log("greater") gen.elseIf(_`${x} < ${num}`) log("smaller") gen.else() log("equal") gen.endIf() gen.optimize() assertEqual( gen, 'if(x > 0){console.log("greater");}else if(x < 0){console.log("smaller");}else {console.log("equal");}' ) }) it("renders `if` statement with `then` and `else` blocks", () => { gen.if( _`${x} > ${num}`, () => log("greater"), () => log("smaller or equal") ) gen.optimize() assertEqual( gen, 'if(x > 0){console.log("greater");}else {console.log("smaller or equal");}' ) }) it("renders `if` statement with `then` block", () => { gen.if(_`${x} > ${num}`, () => log("greater")) assertEqual(gen, 'if(x > 0){console.log("greater");}') }) it("throws exception if `else` block is used without `then` block", () => { assert.throws( () => gen.if(_`${x} > ${num}`, undefined, () => log("smaller or equal")), /"else" body without "then" body/ ) }) it("throws exception if `else` clause is used without `if`", () => { assert.throws(() => gen.else(), /"else" without "if"/) }) it("throws exception if `else` clause is used in another block", () => { gen.func(new Name("f")) assert.throws(() => gen.else(), /"else" without "if"/) }) it("throws exception if `elseIf` clause is used without `if`", () => { assert.throws(() => gen.elseIf(_`${x} > ${num}`), /"else" without "if"/) }) it("throws exception if `elseIf` clause is used in another block", () => { gen.func(new Name("f")) assert.throws(() => gen.elseIf(_`${x} > ${num}`), /"else" without "if"/) }) it("throws exception if `endIf` clause is used without `if`", () => { assert.throws(() => gen.endIf(), /not in block "if\/else"/) }) it("throws exception if `endIf` clause is used in another block", () => { gen.func(new Name("f")) assert.throws(() => gen.endIf(), /not in block "if\/else"/) }) it("renders `if` with negated condition", () => { const gt = gen.const("gt", _`${x} > ${num}`) gen.if(not(gt), () => log("smaller or equal")) assertEqual(gen, 'const gt0 = x > 0;if(!gt0){console.log("smaller or equal");}') }) it("throws exception if `else if` is used after `else`", () => { gen.if(_`${x} > ${num}`) log("greater") gen.else() log("smaller or equal") assert.throws(() => gen.elseIf(_`${x} < ${num}`), /"else" without "if"/) }) const nestedIfCode = 'if(x > 0){console.log("greater");}else {if(x < 0){console.log("smaller");}else {console.log("equal");}}' it("renders nested if statements", () => { gen.if( _`${x} > ${num}`, () => log("greater"), () => gen.if( _`${x} < ${num}`, () => log("smaller"), () => log("equal") ) ) gen.optimize() assertEqual(gen, nestedIfCode) }) it("renders nested if statement with block/endBlock", () => { gen.block() gen.if(_`${x} > ${num}`) log("greater") gen.else().if(_`${x} < ${num}`) log("smaller") gen.else() log("equal") gen.endBlock() gen.optimize() assertEqual(gen, nestedIfCode) }) it("renders nested if statement with block callback-style", () => { gen.block(() => { gen.if(_`${x} > ${num}`) log("greater") gen.else().if(_`${x} < ${num}`) log("smaller") gen.else() log("equal") }) gen.optimize() assertEqual(gen, nestedIfCode) }) function log(comparison: string): void { gen.code(_`console.log(${comparison})`) } }) describe("for statement", () => { const xs = new Name("xs") it("renders `for` for a range", () => { gen.forRange("i", 0, 5, (i: Name) => gen.code(_`console.log(${xs}[${i}])`)) gen.optimize() assertEqual(gen, "for(let i0=0; i0<5; i0++){console.log(xs[i0]);}") }) it("renders `for-of` statement", () => { gen.forOf("x", xs, (x: Name) => gen.code(_`console.log(${x})`)) gen.optimize() assertEqual(gen, "for(const x0 of xs){console.log(x0);}") }) it("renders `for-of` as for with `es5` option", () => { const _gen = getGen({es5: true}) _gen.forOf("x", xs, (x: Name) => _gen.code(_`console.log(${x})`)) _gen.optimize() assertEqual(_gen, "for(var _i0=0; _i0 { gen.forIn("x", xs, (x: Name) => gen.code(_`console.log(${x})`)) gen.optimize() assertEqual(gen, "for(const x0 in xs){console.log(x0);}") }) it("renders `for-in` statement as `for-of` with `ownProperties` option", () => { const _gen = getGen({ownProperties: true}) _gen.forIn("x", xs, (x: Name) => _gen.code(_`console.log(${x})`)) _gen.optimize() assertEqual(_gen, "for(const x0 of Object.keys(xs)){console.log(x0);}") }) it("renders `for-in` statement as `for` with `ownProperties` and `es5` options", () => { const _gen = getGen({ownProperties: true, es5: true}) _gen.forIn("x", xs, (x: Name) => _gen.code(_`console.log(${x})`)) _gen.optimize() assertEqual( _gen, "var _arr0 = Object.keys(xs);for(var _i0=0; _i0<_arr0.length; _i0++){var x0 = _arr0[_i0];console.log(x0);}" ) }) const nestendFor = "let i0 = arr0.length;let j0;outer0:for(;i0--;){for(j0=i0;j0--;){if(arr0[i0] === arr0[j0]){break outer0;}}}" it("renders generic clause `for` with `label` and `break` in self-balancing block", () => { const outer = gen.name("outer") const arr = gen.name("arr") const i = gen.let("i", _`${arr}.length`) const j = gen.let("j") gen .block() .label(outer) .for(_`;${i}--;`) .for(_`${j}=${i};${j}--;`) .if(_`${arr}[${i}] === ${arr}[${j}]`) .break(outer) .endBlock() gen.optimize() assertEqual(gen, nestendFor) }) it("renders generic statement `for` with `label` and `break`", () => { const outer = gen.name("outer") const arr = gen.name("arr") const i = gen.let("i", _`${arr}.length`) const j = gen.let("j") gen .label(outer) .for(_`;${i}--;`, () => gen.for(_`${j}=${i};${j}--;`, () => gen.if(_`${arr}[${i}] === ${arr}[${j}]`, () => gen.break(outer)) ) ) gen.optimize() assertEqual(gen, nestendFor) }) }) describe("function definition", () => { it("renders function with `return` and `try` statements", () => { const inverse = new Name("inverse") const x = gen.name("x") gen .func(inverse, x) .try( () => gen.return(_`1/${x}`), (e) => gen.code(_`console.error(${str`dividing ${x} by 0`})`).throw(e) ) .endFunc() gen.optimize() assertEqual( gen, 'function inverse(x0){try{return 1/x0;}catch(e0){console.error("dividing "+x0+" by 0");throw e0;}}' ) }) }) describe("`try` statement", () => { it("should render `try/catch/finally`", () => { gen.try(_`log("try")`, (e) => gen.code(_`log(${e})`), _`log("fin")`) gen.optimize() assertEqual(gen, 'try{log("try");}catch(e0){log(e0);}finally{log("fin");}') }) it("should render `try/finally`", () => { gen.try(_`log("try")`, undefined, _`log("fin")`) gen.optimize() assertEqual(gen, 'try{log("try");}finally{log("fin");}') }) }) describe("code optimization", () => { const valid = new Name("valid") it("should remove empty `if`", () => { gen.if(valid).endIf() assertEqual(gen, "if(valid){}") gen.optimize() assertEqual(gen, "") }) it("should remove empty `else`", () => { gen .if(valid) .code(_`log("if")`) .else() .endIf() assertEqual(gen, 'if(valid){log("if");}else {}') gen.optimize() assertEqual(gen, 'if(valid){log("if");}') }) it("should remove `else` from always valid `if` condition", () => { gen .if(true) .code(_`log("if")`) .else() .code(_`log("else")`) .endIf() assertEqual(gen, 'if(true){log("if");}else {log("else");}') gen.optimize() assertEqual(gen, 'log("if");') }) it("should remove `if` from always invalid `if` condition", () => { gen .if(false) .code(_`log("if")`) .else() .code(_`log("else")`) .endIf() assertEqual(gen, 'if(false){log("if");}else {log("else");}') gen.optimize() assertEqual(gen, 'log("else");') }) it("should remove empty `if` and keep `else`", () => { gen .if(valid) .else() .code(_`log("else")`) .endIf() assertEqual(gen, 'if(valid){}else {log("else");}') gen.optimize() assertEqual(gen, 'if(!valid){log("else");}') }) it("should remove empty `for`", () => { gen.for(_`const x of xs`).endFor() assertEqual(gen, "for(const x of xs){}") gen.optimize() assertEqual(gen, "") }) it("should remove unused names", () => { gen.const("x", 0) assertEqual(gen, "const x0 = 0;") gen.optimize() assertEqual(gen, "") }) it("should remove names used in removed branches", () => { const x = gen.const("x", 0) gen.if(_`${x} === 0`).endIf() assertEqual(gen, "const x0 = 0;if(x0 === 0){}") gen.optimize() assertEqual(gen, "") }) it('should replace names with "constant" expressions if used only once', () => { const data = new Name("data") const x = gen.const("x", _`${data}.prop`, true) // true means that the expression `data.prop` is "constant" gen .if(_`${x} === 0`) .code(_`log()`) .endIf() assertEqual(gen, "const x0 = data.prop;if(x0 === 0){log();}") gen.optimize() assertEqual(gen, "if(data.prop === 0){log();}") }) }) }) describe("external scope", () => { let gen: CodeGen let scope: ScopeStore beforeEach(() => { scope = {} gen = new CodeGen(new ValueScope({scope})) }) it("defines and renders value references and values code", () => { gen.scopeValue("val", {ref: 1, code: _`1`}) assert.deepEqual(gen.getScopeValue("val", 1)?.value, { ref: 1, code: _`1`, }) assertEqual(gen.scopeRefs(new Name("scope")), "const val0 = scope.val[0];") assertEqual(gen.scopeCode(), "const val0 = 1;") }) }) }) function assertEqual(code: Code | CodeGen, s: string): void { assert.strictEqual(code.toString(), s) } function getGen(opts?: CodeGenOptions): CodeGen { return new CodeGen(new ValueScope({scope: {}}), opts) } ================================================ FILE: spec/coercion.spec.ts ================================================ import type Ajv from ".." import _Ajv from "./ajv" import chai from "./chai" chai.should() const coercionRules = { string: { number: [ {from: 1, to: "1"}, {from: 1.5, to: "1.5"}, {from: 2e100, to: "2e+100"}, ], boolean: [ {from: false, to: "false"}, {from: true, to: "true"}, ], null: [{from: null, to: ""}], object: [{from: {}, to: undefined}], array: [ {from: [], to: undefined}, {from: [1], to: undefined}, ], }, number: { string: [ {from: "1", to: 1}, {from: "1.5", to: 1.5}, {from: "2e10", to: 2e10}, {from: "1a", to: undefined}, {from: "abc", to: undefined}, {from: "", to: undefined}, ], boolean: [ {from: false, to: 0}, {from: true, to: 1}, ], null: [{from: null, to: 0}], object: [{from: {}, to: undefined}], array: [ {from: [], to: undefined}, {from: [true], to: undefined}, ], }, integer: { string: [ {from: "1", to: 1}, {from: "1.5", to: undefined}, {from: "2e10", to: 2e10}, {from: "1a", to: undefined}, {from: "abc", to: undefined}, {from: "", to: undefined}, ], boolean: [ {from: false, to: 0}, {from: true, to: 1}, ], null: [{from: null, to: 0}], object: [{from: {}, to: undefined}], array: [ {from: [], to: undefined}, {from: ["1"], to: undefined}, ], }, boolean: { string: [ {from: "false", to: false}, {from: "true", to: true}, {from: "", to: undefined}, {from: "abc", to: undefined}, ], number: [ {from: 0, to: false}, {from: 1, to: true}, {from: 2, to: undefined}, {from: 2.5, to: undefined}, ], null: [{from: null, to: false}], object: [{from: {}, to: undefined}], array: [ {from: [], to: undefined}, {from: [0], to: undefined}, ], }, null: { string: [ {from: "", to: null}, {from: "abc", to: undefined}, {from: "null", to: undefined}, ], number: [ {from: 0, to: null}, {from: 1, to: undefined}, ], boolean: [ {from: false, to: null}, {from: true, to: undefined}, ], object: [{from: {}, to: undefined}], array: [ {from: [], to: undefined}, {from: [null], to: undefined}, ], }, array: { all: [ {type: "string", from: "abc", to: undefined}, {type: "number", from: 1, to: undefined}, {type: "boolean", from: true, to: undefined}, {type: "null", from: null, to: undefined}, {type: "object", from: {}, to: undefined}, ], }, object: { all: [ {type: "string", from: "abc", to: undefined}, {type: "number", from: 1, to: undefined}, {type: "boolean", from: true, to: undefined}, {type: "null", from: null, to: undefined}, {type: "array", from: [], to: undefined}, ], }, } const coercionArrayRules = JSON.parse(JSON.stringify(coercionRules)) coercionArrayRules.string.array = [ {from: ["abc"], to: "abc"}, {from: [123], to: "123"}, {from: [true], to: "true"}, {from: [null], to: ""}, {from: [{}], to: undefined}, {from: ["abc", "def"], to: undefined}, {from: [], to: undefined}, ] coercionArrayRules.number.array = [ {from: [1.5], to: 1.5}, {from: ["1.5"], to: 1.5}, {from: [true], to: 1}, {from: [null], to: 0}, {from: ["abc"], to: undefined}, {from: [{}], to: undefined}, ] coercionArrayRules.integer.array = [ {from: [1], to: 1}, {from: ["1"], to: 1}, {from: [true], to: 1}, {from: [null], to: 0}, {from: [1.5], to: undefined}, {from: ["abc"], to: undefined}, {from: [{}], to: undefined}, ] coercionArrayRules.boolean.array = [ {from: [true], to: true}, {from: ["true"], to: true}, {from: [1], to: true}, {from: [null], to: false}, {from: ["abc"], to: undefined}, {from: [2], to: undefined}, {from: [{}], to: undefined}, ] coercionArrayRules.null.array = [ {from: [null], to: null}, {from: [""], to: null}, {from: [0], to: null}, {from: [false], to: null}, {from: ["abc"], to: undefined}, {from: [1], to: undefined}, {from: [true], to: undefined}, {from: [{}], to: undefined}, ] coercionArrayRules.object.array = [{from: [{}], to: undefined}] coercionArrayRules.array = { string: [{from: "abc", to: ["abc"]}], number: [{from: 1, to: [1]}], boolean: [{from: true, to: [true]}], null: [{from: null, to: [null]}], object: [{from: {}, to: undefined}], } describe("Type coercion", () => { let ajv: Ajv, fullAjv: Ajv, instances: Ajv[] beforeEach(() => { ajv = new _Ajv({coerceTypes: true, verbose: true, allowUnionTypes: true}) fullAjv = new _Ajv({coerceTypes: true, verbose: true, allErrors: true, allowUnionTypes: true}) instances = [ajv, fullAjv] }) it("should coerce scalar values", () => { testRules(coercionRules, (test, schema, canCoerce /*, toType, fromType*/) => { instances.forEach((_ajv) => { const valid = _ajv.validate(schema, test.from) //if (valid !== canCoerce) console.log('true', toType, fromType, test, ajv.errors); valid.should.equal(canCoerce) }) }) }) it("should coerce scalar values (coerceTypes = array)", () => { ajv = new _Ajv({coerceTypes: "array", verbose: true}) fullAjv = new _Ajv({coerceTypes: "array", verbose: true, allErrors: true}) instances = [ajv, fullAjv] testRules(coercionArrayRules, (test, schema, canCoerce, toType, fromType) => { instances.forEach((_ajv) => { const valid = _ajv.validate(schema, test.from) if (valid !== canCoerce) console.log(toType, ".", fromType, test, schema, ajv.errors) valid.should.equal(canCoerce) }) }) }) it("should coerce values in objects/arrays and update properties/items", () => { testRules(coercionRules, (test, schema, canCoerce /*, toType, fromType*/) => { const schemaObject = { type: "object", properties: { foo: schema, }, } const schemaArray = { type: "array", items: schema, } const schemaArrObj = { type: "array", items: schemaObject, } instances.forEach((_ajv) => { testCoercion(_ajv, schemaArray, [test.from], [test.to]) testCoercion(_ajv, schemaObject, {foo: test.from}, {foo: test.to}) testCoercion(_ajv, schemaArrObj, [{foo: test.from}], [{foo: test.to}]) }) function testCoercion(_ajv, _schema, fromData, toData) { const valid = _ajv.validate(_schema, fromData) //if (valid !== canCoerce) console.log(schema, fromData, toData); valid.should.equal(canCoerce) if (valid) fromData.should.eql(toData) } }) }) it("should coerce to multiple types in order with number type", () => { const schema = { type: "object", properties: { foo: { type: ["number", "boolean", "null"], }, }, } instances.forEach((_ajv) => { let data _ajv.validate(schema, (data = {foo: "1"})).should.equal(true) data.should.eql({foo: 1}) _ajv.validate(schema, (data = {foo: "1.5"})).should.equal(true) data.should.eql({foo: 1.5}) _ajv.validate(schema, (data = {foo: "false"})).should.equal(true) data.should.eql({foo: false}) _ajv.validate(schema, (data = {foo: 1})).should.equal(true) data.should.eql({foo: 1}) // no coercion _ajv.validate(schema, (data = {foo: true})).should.equal(true) data.should.eql({foo: true}) // no coercion _ajv.validate(schema, (data = {foo: null})).should.equal(true) data.should.eql({foo: null}) // no coercion _ajv.validate(schema, (data = {foo: "abc"})).should.equal(false) data.should.eql({foo: "abc"}) // can't coerce _ajv.validate(schema, (data = {foo: {}})).should.equal(false) data.should.eql({foo: {}}) // can't coerce _ajv.validate(schema, (data = {foo: []})).should.equal(false) data.should.eql({foo: []}) // can't coerce }) }) it("should coerce to multiple types in order with integer type", () => { const schema = { type: "object", properties: { foo: { type: ["integer", "boolean", "null"], }, }, } instances.forEach((_ajv) => { let data _ajv.validate(schema, (data = {foo: "1"})).should.equal(true) data.should.eql({foo: 1}) _ajv.validate(schema, (data = {foo: "false"})).should.equal(true) data.should.eql({foo: false}) _ajv.validate(schema, (data = {foo: 1})).should.equal(true) data.should.eql({foo: 1}) // no coercion _ajv.validate(schema, (data = {foo: true})).should.equal(true) data.should.eql({foo: true}) // no coercion _ajv.validate(schema, (data = {foo: null})).should.equal(true) data.should.eql({foo: null}) // no coercion _ajv.validate(schema, (data = {foo: "abc"})).should.equal(false) data.should.eql({foo: "abc"}) // can't coerce _ajv.validate(schema, (data = {foo: {}})).should.equal(false) data.should.eql({foo: {}}) // can't coerce _ajv.validate(schema, (data = {foo: []})).should.equal(false) data.should.eql({foo: []}) // can't coerce }) }) it("should fail to coerce non-number if multiple properties/items are coerced (issue #152)", () => { const schema = { type: "object", properties: { foo: {type: "number"}, bar: {type: "number"}, }, } const schema2 = { type: "array", items: {type: "number"}, } instances.forEach((_ajv) => { const data: any = {foo: "123", bar: "bar"} _ajv.validate(schema, data).should.equal(false) data.should.eql({foo: 123, bar: "bar"}) const data2: any = ["123", "bar"] _ajv.validate(schema2, data2).should.equal(false) data2.should.eql([123, "bar"]) }) }) it("should update data if the schema is in ref that is not inlined", () => { instances.push(new _Ajv({coerceTypes: true, inlineRefs: false, allowUnionTypes: true})) const schema = { type: "object", definitions: { foo: {type: "number"}, }, properties: { foo: {$ref: "#/definitions/foo"}, }, } const schema2 = { type: "object", definitions: { foo: { // allOf is needed to make sure that "foo" is compiled to a separate function // and not simply passed through (as it would be if it were only $ref) allOf: [{$ref: "#/definitions/bar"}], }, bar: {type: "number"}, }, properties: { foo: {$ref: "#/definitions/foo"}, }, } const schemaRecursive = { type: ["object", "number"], properties: { foo: {$ref: "#"}, }, } const schemaRecursive2 = { $id: "http://e.com/schema.json#", definitions: { foo: { $id: "http://e.com/foo.json#", type: ["object", "number"], properties: { foo: {$ref: "#"}, }, }, }, type: "object", properties: { foo: {$ref: "http://e.com/foo.json#"}, }, } instances.forEach((_ajv) => { testCoercion(schema, {foo: "1"}, {foo: 1}) testCoercion(schema2, {foo: "1"}, {foo: 1}) testCoercion(schemaRecursive, {foo: {foo: "1"}}, {foo: {foo: 1}}) testCoercion(schemaRecursive2, {foo: {foo: {foo: "1"}}}, {foo: {foo: {foo: 1}}}) function testCoercion(_schema, fromData, toData) { const valid = _ajv.validate(_schema, fromData) // if (!valid) console.log(schema, fromData, toData); valid.should.equal(true) fromData.should.eql(toData) } }) }) it("should generate one error for type with coerceTypes option (issue #469)", () => { const schema = { type: "number", minimum: 10, } instances.forEach((_ajv) => { const validate = _ajv.compile(schema) validate(9).should.equal(false) validate.errors?.length.should.equal(1) validate(11).should.equal(true) validate("foo").should.equal(false) validate.errors?.length.should.equal(1) }) }) it('should check "uniqueItems" after coercion', () => { const schema = { type: "array", items: {type: "number"}, uniqueItems: true, } instances.forEach((_ajv) => { const validate = _ajv.compile(schema) validate([1, "2", 3]).should.equal(true) validate([1, "2", 2]).should.equal(false) validate.errors?.length.should.equal(1) validate.errors?.[0].keyword.should.equal("uniqueItems") }) }) it('should check "contains" after coercion', () => { const schema = { type: "array", items: {type: "number"}, contains: {const: 2}, } instances.forEach((_ajv) => { const validate = _ajv.compile(schema) validate([1, "2", 3]).should.equal(true) validate([1, "3", 4]).should.equal(false) validate.errors?.pop()?.keyword.should.equal("contains") }) }) function testRules(rules, cb) { for (const toType in rules) { for (const fromType in rules[toType]) { const tests = rules[toType][fromType] tests.forEach((test) => { const canCoerce = test.to !== undefined const schema = canCoerce ? Array.isArray(test.to) ? {type: toType, items: {type: fromType, enum: [test.to[0]]}} : {type: toType, enum: [test.to]} : {type: toType} cb(test, schema, canCoerce, toType, fromType) }) } } } }) ================================================ FILE: spec/discriminator.spec.ts ================================================ import type Ajv from ".." import type AjvPack from "../dist/standalone/instance" import type AjvCore from "../dist/core" import type {SchemaObject} from ".." import _Ajv from "./ajv" import _Ajv2019 from "./ajv2019" import getAjvInstances from "./ajv_instances" import {withStandalone} from "./ajv_standalone" import options from "./ajv_options" import * as assert from "assert" describe("discriminator keyword", function () { let ajvs: (Ajv | AjvPack)[] this.timeout(10000) before(() => { ajvs = [...getAjvs(_Ajv), ...getAjvs(_Ajv2019)] }) function getAjvs(AjvClass: typeof AjvCore) { return withStandalone(getAjvInstances(AjvClass, options, {discriminator: true})) } describe("validation", () => { const schema1 = { type: "object", discriminator: {propertyName: "foo"}, oneOf: [ { properties: { foo: {const: "x"}, a: {type: "string"}, }, required: ["foo", "a"], }, { properties: { foo: {enum: ["y", "z"]}, b: {type: "string"}, }, required: ["foo", "b"], }, ], } const schema2 = { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [ { properties: { foo: {const: "x"}, a: {type: "string"}, }, required: ["a"], }, { properties: { foo: {enum: ["y", "z"]}, b: {type: "string"}, }, required: ["b"], }, ], } const schemas = [schema1, schema2] it("should validate data", () => { assertValid(schemas, {foo: "x", a: "a"}) assertValid(schemas, {foo: "y", b: "b"}) assertValid(schemas, {foo: "z", b: "b"}) assertInvalid(schemas, {}) assertInvalid(schemas, {foo: 1}) assertInvalid(schemas, {foo: "bar"}) assertInvalid(schemas, {foo: "x", b: "b"}) assertInvalid(schemas, {foo: "y", a: "a"}) assertInvalid(schemas, {foo: "z", a: "a"}) }) }) describe("validation with referenced schemas", () => { const definitions1 = { schema1: { properties: { foo: {const: "x"}, a: {type: "string"}, }, required: ["foo", "a"], }, schema2: { properties: { foo: {enum: ["y", "z"]}, b: {type: "string"}, }, required: ["foo", "b"], }, } const mainSchema1 = { type: "object", discriminator: {propertyName: "foo"}, oneOf: [ { $ref: "#/definitions/schema1", }, { $ref: "#/definitions/schema2", }, ], } const definitions2 = { schema1: { properties: { foo: {const: "x"}, a: {type: "string"}, }, required: ["a"], }, schema2: { properties: { foo: {enum: ["y", "z"]}, b: {type: "string"}, }, required: ["b"], }, } const mainSchema2 = { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [ { $ref: "#/definitions/schema1", }, { $ref: "#/definitions/schema2", }, ], } const schema = [ {definitions: definitions1, ...mainSchema1}, {definitions: definitions2, ...mainSchema2}, ] it("should validate data", () => { assertValid(schema, {foo: "x", a: "a"}) assertValid(schema, {foo: "y", b: "b"}) assertValid(schema, {foo: "z", b: "b"}) assertInvalid(schema, {}) assertInvalid(schema, {foo: 1}) assertInvalid(schema, {foo: "bar"}) assertInvalid(schema, {foo: "x", b: "b"}) assertInvalid(schema, {foo: "y", a: "a"}) assertInvalid(schema, {foo: "z", a: "a"}) }) }) describe("schema with external $refs", () => { const schemas = { main: { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [ { $ref: "schema1", }, { $ref: "schema2", }, ], }, schema1: { type: "object", properties: { foo: {const: "x"}, }, }, schema2: { type: "object", properties: { foo: {enum: ["y", "z"]}, }, }, } const data = {foo: "x"} const badData = {foo: "w"} it("compile should resolve each $ref to a schema that was added with addSchema", () => { const opts = { discriminator: true, } const ajv = new _Ajv(opts) ajv.addSchema(schemas.main, "https://host/main") ajv.addSchema(schemas.schema1, "https://host/schema1") ajv.addSchema(schemas.schema2, "https://host/schema2") const validate = ajv.compile({$ref: "https://host/main"}) assert.strictEqual(validate(data), true) assert.strictEqual(validate(badData), false) }) it("compileAsync should loadSchema each $ref", async () => { const opts = { discriminator: true, loadSchema(url) { if (!url.startsWith("https://host/")) return undefined const name = url.substring("https://host/".length) return schemas[name] }, } const ajv = new _Ajv(opts) const validate = await ajv.compileAsync({$ref: "https://host/main"}) assert.strictEqual(validate(data), true) assert.strictEqual(validate(badData), false) }) }) describe("validation with deeply referenced schemas", () => { const schema = [ { type: "object", properties: { container: { $ref: "#/definitions/Container", }, }, definitions: { BlockA: { type: "object", properties: { _type: { type: "string", enum: ["a"], }, a: {type: "string"}, }, additionalProperties: false, required: ["_type"], title: "BlockA", }, BlockB: { type: "object", properties: { _type: { type: "string", enum: ["b"], }, b: {type: "string"}, }, additionalProperties: false, required: ["_type"], title: "BlockB", }, Container: { type: "object", properties: { list: { type: "array", items: { oneOf: [ { $ref: "#/definitions/BlockA", }, { $ref: "#/definitions/BlockB", }, ], discriminator: { propertyName: "_type", }, }, }, }, }, }, }, ] it("should validate data", () => { assertValid(schema, { container: { list: [ {_type: "a", a: "foo"}, {_type: "b", b: "bar"}, ], }, }) assertInvalid(schema, { container: { list: [ {_type: "a", b: "foo"}, {_type: "b", b: "bar"}, ], }, }) }) }) describe("valid schemas", () => { it("should have oneOf", () => { invalidSchema( {type: "object", discriminator: {propertyName: "foo"}}, /discriminator: requires oneOf/ ) }) it("should have schema for tag", () => { invalidSchema( { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [{properties: {}}], }, /discriminator: oneOf subschemas \(or referenced schemas\) must have "properties\/foo"/ ) }) it("should have enum or const in schema for tag", () => { invalidSchema( { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [{properties: {foo: {}}}], }, /discriminator: "properties\/foo" must have "const" or "enum"/ ) }) it("tag value should be string", () => { invalidSchema( { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [{properties: {foo: {const: 1}}}], }, /discriminator: "foo" values must be unique strings/ ) }) it("tag values should be unique", () => { invalidSchema( { type: "object", discriminator: {propertyName: "foo"}, required: ["foo"], oneOf: [{properties: {foo: {const: "a"}}}, {properties: {foo: {const: "a"}}}], }, /discriminator: "foo" values must be unique strings/ ) }) it("tag should be required", () => { invalidSchema( { type: "object", discriminator: {propertyName: "foo"}, oneOf: [ {properties: {foo: {const: "a"}}, required: ["foo"]}, {properties: {foo: {const: "b"}}}, ], }, /discriminator: "foo" must be required/ ) }) }) function assertValid(schemas: SchemaObject[], data: unknown): void { schemas.forEach((schema) => ajvs.forEach((ajv) => assert.strictEqual(ajv.validate(schema, data), true)) ) } function assertInvalid(schemas: SchemaObject[], data: unknown): void { schemas.forEach((schema) => ajvs.forEach((ajv) => assert.strictEqual(ajv.validate(schema, data), false)) ) } function invalidSchema(schema: SchemaObject, error: any): void { ajvs.forEach((ajv) => assert.throws(() => ajv.compile(schema), error)) } }) ================================================ FILE: spec/dynamic-ref.spec.ts ================================================ import type Ajv from "../dist/core" import type {SchemaObject} from ".." import _Ajv from "./ajv2019" import getAjvInstances from "./ajv_instances" import options from "./ajv_options" import assert = require("assert") describe("recursiveRef and dynamicRef", () => { let ajvs: Ajv[] beforeEach(() => { ajvs = getAjvInstances(_Ajv, options) }) describe("recursiveRef", () => { it("should allow extending recursive schema with recursiveRef (draft2019-09)", () => { const treeSchema = { $id: "https://example.com/tree", $recursiveAnchor: true, type: "object", required: ["data"], properties: { data: true, children: { type: "array", items: { $recursiveRef: "#", }, }, }, } const strictTreeSchema = { $id: "https://example.com/strict-tree", $recursiveAnchor: true, $ref: "tree", type: "object", unevaluatedProperties: false, } testTree(treeSchema, strictTreeSchema) }) }) describe("dynamicRef", () => { it("should allow extending recursive schema with dynamicRef (future draft2020)", () => { const treeSchema = { $id: "https://example.com/tree", $dynamicAnchor: "node", type: "object", required: ["data"], properties: { data: true, children: { type: "array", items: { $dynamicRef: "#node", }, }, }, } const strictTreeSchema = { $id: "https://example.com/strict-tree", $dynamicAnchor: "node", $ref: "tree", type: "object", unevaluatedProperties: false, } testTree(treeSchema, strictTreeSchema) }) }) function testTree(treeSchema: SchemaObject, strictTreeSchema: SchemaObject): void { const validTree = { data: 1, children: [ { data: 2, children: [{data: 3}], }, ], } const invalidTree = { data: 1, children: [ { data: 2, children: {}, }, ], } const treeWithExtra = { data: 1, children: [{data: 2, extra: 2}], } const treeWithDeepExtra = { data: 1, children: [ { data: 2, children: [{data: 3, extra: 3}], }, ], } ajvs.forEach((ajv) => { const validate = ajv.compile(treeSchema) assert.strictEqual(validate(validTree), true) assert.strictEqual(validate(invalidTree), false) assert.strictEqual(validate(treeWithExtra), true) // because unevaluated props allowed assert.strictEqual(validate(treeWithDeepExtra), true) // because unevaluated props allowed const validateStrict = ajv.compile(strictTreeSchema) assert.strictEqual(validateStrict(validTree), true) assert.strictEqual(validateStrict(invalidTree), false) assert.strictEqual(validateStrict(treeWithExtra), false) // because "extra" is "unevaluated" assert.strictEqual(validateStrict(treeWithDeepExtra), false) // because "extra" is "unevaluated" }) } }) ================================================ FILE: spec/errors.spec.ts ================================================ import type Ajv from ".." import type {ValidateFunction} from ".." import _Ajv from "./ajv" import chai from "./chai" const should = chai.should() describe("Validation errors", () => { let ajv: Ajv, ajvJP: Ajv, fullAjv: Ajv beforeEach(() => { createInstances() }) function createInstances() { const opts = { loopRequired: 21, strictTypes: false, strictTuples: false, } ajv = new _Ajv(opts) ajvJP = new _Ajv(opts) fullAjv = new _Ajv({ ...opts, allErrors: true, verbose: true, jsPropertySyntax: true, // deprecated logger: false, }) } it("error should include instancePath", () => { const schema = { properties: { foo: {type: "number"}, }, } testSchema1(schema) }) it('"refs" error should include instancePath', () => { const schema = { definitions: { num: {type: "number"}, }, properties: { foo: {$ref: "#/definitions/num"}, }, } testSchema1(schema, "#/definitions/num") }) describe('"additionalProperties" errors', () => { it("should NOT include property in instancePath", () => { testAdditional() }) function testAdditional() { const schema = { properties: { foo: {}, bar: {}, }, additionalProperties: false, } const data = {foo: 1, bar: 2}, invalidData = {foo: 1, bar: 2, baz: 3, quux: 4} const msg = "must NOT have additional properties" const validate = ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData) shouldBeError( validate.errors?.[0], "additionalProperties", "#/additionalProperties", "", msg, { additionalProperty: "baz", } ) const validateJP = ajvJP.compile(schema) shouldBeValid(validateJP, data) shouldBeInvalid(validateJP, invalidData) shouldBeError( validateJP.errors?.[0], "additionalProperties", "#/additionalProperties", "", msg, {additionalProperty: "baz"} ) const fullValidate = fullAjv.compile(schema) shouldBeValid(fullValidate, data) shouldBeInvalid(fullValidate, invalidData, 2) shouldBeError( fullValidate.errors?.[0], "additionalProperties", "#/additionalProperties", "", msg, {additionalProperty: "baz"} ) shouldBeError( fullValidate.errors?.[1], "additionalProperties", "#/additionalProperties", "", msg, {additionalProperty: "quux"} ) } }) describe('errors when "additionalProperties" is schema', () => { it("should NOT include property in instancePath", () => { testAdditionalIsSchema() }) function testAdditionalIsSchema() { const schema = { properties: { foo: {type: "integer"}, bar: {type: "integer"}, }, additionalProperties: { type: "object", properties: { quux: {type: "string"}, }, }, } const data = {foo: 1, bar: 2, baz: {quux: "abc"}}, invalidData = {foo: 1, bar: 2, baz: {quux: 3}, boo: {quux: 4}} const schPath = "#/additionalProperties/properties/quux/type" const validate = ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData) shouldBeError(validate.errors?.[0], "type", schPath, "/baz/quux", "must be string", { type: "string", }) const validateJP = ajvJP.compile(schema) shouldBeValid(validateJP, data) shouldBeInvalid(validateJP, invalidData) shouldBeError(validateJP.errors?.[0], "type", schPath, "/baz/quux", "must be string", { type: "string", }) const fullValidate = fullAjv.compile(schema) shouldBeValid(fullValidate, data) shouldBeInvalid(fullValidate, invalidData, 2) shouldBeError(fullValidate.errors?.[0], "type", schPath, "['baz'].quux", "must be string", { type: "string", }) shouldBeError(fullValidate.errors?.[1], "type", schPath, "['boo'].quux", "must be string", { type: "string", }) } }) describe('"required" errors', () => { it("should NOT include missing property in instancePath", () => { testRequired() }) function testRequired() { const schema = { required: ["foo", "bar", "baz"], } _testRequired(schema, "#") } it("large data/schemas", () => { testRequiredLargeSchema() }) function testRequiredLargeSchema() { let schema: any = {required: []} const data = {}, invalidData1 = {}, invalidData2 = {} for (let i = 0; i < 100; i++) { schema.required.push("" + i) // properties from '0' to '99' are required data[i] = invalidData1[i] = invalidData2[i] = i } delete invalidData1[1] // property '1' will be missing delete invalidData2[2] // properties '2' and '198' will be missing delete invalidData2[98] test() schema = {anyOf: [schema]} test(1, "#/anyOf/0") function test(extraErrors = 0, schemaPathPrefix = "#") { const schPath = schemaPathPrefix + "/required" const validate = ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData1, 1 + extraErrors) shouldBeError(validate.errors?.[0], "required", schPath, "", requiredMsg("1"), { missingProperty: "1", }) shouldBeInvalid(validate, invalidData2, 1 + extraErrors) shouldBeError(validate.errors?.[0], "required", schPath, "", requiredMsg("2"), { missingProperty: "2", }) const validateJP = ajvJP.compile(schema) shouldBeValid(validateJP, data) shouldBeInvalid(validateJP, invalidData1, 1 + extraErrors) shouldBeError(validateJP.errors?.[0], "required", schPath, "", requiredMsg("1"), { missingProperty: "1", }) shouldBeInvalid(validateJP, invalidData2, 1 + extraErrors) shouldBeError(validateJP.errors?.[0], "required", schPath, "", requiredMsg("2"), { missingProperty: "2", }) const fullValidate = fullAjv.compile(schema) shouldBeValid(fullValidate, data) shouldBeInvalid(fullValidate, invalidData1, 1 + extraErrors) shouldBeError(fullValidate.errors?.[0], "required", schPath, "", requiredMsg("1"), { missingProperty: "1", }) shouldBeInvalid(fullValidate, invalidData2, 2 + extraErrors) shouldBeError(fullValidate.errors?.[0], "required", schPath, "", requiredMsg("2"), { missingProperty: "2", }) shouldBeError(fullValidate.errors?.[1], "required", schPath, "", requiredMsg("98"), { missingProperty: "98", }) } } it('with "properties"', () => { testRequiredAndProperties() }) function testRequiredAndProperties() { const schema = { properties: { foo: {type: "number"}, bar: {type: "number"}, baz: {type: "number"}, }, required: ["foo", "bar", "baz"], } _testRequired(schema) } it('in "anyOf"', () => { testRequiredInAnyOf() }) function testRequiredInAnyOf() { const schema = { anyOf: [{required: ["foo", "bar", "baz"]}], } _testRequired(schema, "#/anyOf/0", 1) } it("should not validate required twice in large schemas with loopRequired option", () => { ajv = new _Ajv({loopRequired: 1, allErrors: true}) const schema = { type: "object", properties: { foo: {type: "integer"}, bar: {type: "integer"}, }, required: ["foo", "bar"], } const validate = ajv.compile(schema) validate({}).should.equal(false) validate.errors?.should.have.length(2) }) it("should not validate required twice with $data ref", () => { ajv = new _Ajv({$data: true, allErrors: true}) const schema = { type: "object", properties: { foo: {type: "integer"}, bar: {type: "integer"}, }, required: {$data: "0/requiredProperties"}, } const validate = ajv.compile(schema) validate({requiredProperties: ["foo", "bar"]}).should.equal(false) validate.errors?.should.have.length(2) }) it("should show different error when required is $data of incorrect type", () => { test(new _Ajv({$data: true})) test(new _Ajv({$data: true, allErrors: true})) function test(_ajv: Ajv) { const schema = { type: "object", required: {$data: "0/req"}, properties: { req: {}, foo: {}, bar: {}, }, } const validate = _ajv.compile(schema) shouldBeValid(validate, {req: ["foo", "bar"], foo: 1, bar: 2}) shouldBeInvalid(validate, {req: ["foo", "bar"], foo: 1}) shouldBeError( validate.errors?.[0], "required", "#/required", "", "must have required property 'bar'", {missingProperty: "bar"} ) shouldBeInvalid(validate, {req: "invalid"}) shouldBeError( validate.errors?.[0], "required", "#/required", "", '"required" keyword must be array ($data)', {} ) } }) it("should include missing property with ownProperties option (issue #1493)", () => { test(new _Ajv()) test(new _Ajv({ownProperties: true})) function test(_ajv: Ajv): void { const schema = { type: "object", required: ["a"], properties: { a: {type: "string"}, }, } const validate = _ajv.compile(schema) shouldBeValid(validate, {a: "abc"}) shouldBeInvalid(validate, {}) shouldBeError( validate.errors?.[0], "required", "#/required", "", "must have required property 'a'", {missingProperty: "a"} ) } }) }) describe('"dependencies" errors', () => { it("should NOT include missing property in instancePath", () => { testDependencies() }) function testDependencies() { const schema = { dependencies: { a: ["foo", "bar", "baz"], }, } const data = {a: 0, foo: 1, bar: 2, baz: 3}, invalidData1 = {a: 0, foo: 1, baz: 3}, invalidData2 = {a: 0, bar: 2} const msg = "must have properties foo, bar, baz when property a is present" const validate = ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData1) shouldBeError(validate.errors?.[0], "dependencies", "#/dependencies", "", msg, params("bar")) shouldBeInvalid(validate, invalidData2) shouldBeError(validate.errors?.[0], "dependencies", "#/dependencies", "", msg, params("foo")) const validateJP = ajvJP.compile(schema) shouldBeValid(validateJP, data) shouldBeInvalid(validateJP, invalidData1) shouldBeError( validateJP.errors?.[0], "dependencies", "#/dependencies", "", msg, params("bar") ) shouldBeInvalid(validateJP, invalidData2) shouldBeError( validateJP.errors?.[0], "dependencies", "#/dependencies", "", msg, params("foo") ) const fullValidate = fullAjv.compile(schema) shouldBeValid(fullValidate, data) shouldBeInvalid(fullValidate, invalidData1) shouldBeError( fullValidate.errors?.[0], "dependencies", "#/dependencies", "", msg, params("bar") ) shouldBeInvalid(fullValidate, invalidData2, 2) shouldBeError( fullValidate.errors?.[0], "dependencies", "#/dependencies", "", msg, params("foo") ) shouldBeError( fullValidate.errors?.[1], "dependencies", "#/dependencies", "", msg, params("baz") ) function params(missing) { const p = { property: "a", deps: "foo, bar, baz", depsCount: 3, missingProperty: missing, } return p } } }) function _testRequired(schema, schemaPathPrefix = "#", extraErrors = 0) { const schPath = schemaPathPrefix + "/required" const data = {foo: 1, bar: 2, baz: 3}, invalidData1 = {foo: 1, baz: 3}, invalidData2 = {bar: 2} const validate = ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData1, 1 + extraErrors) shouldBeError(validate.errors?.[0], "required", schPath, "", requiredMsg("bar"), { missingProperty: "bar", }) shouldBeInvalid(validate, invalidData2, 1 + extraErrors) shouldBeError(validate.errors?.[0], "required", schPath, "", requiredMsg("foo"), { missingProperty: "foo", }) const validateJP = ajvJP.compile(schema) shouldBeValid(validateJP, data) shouldBeInvalid(validateJP, invalidData1, 1 + extraErrors) shouldBeError(validateJP.errors?.[0], "required", schPath, "", requiredMsg("bar"), { missingProperty: "bar", }) shouldBeInvalid(validateJP, invalidData2, 1 + extraErrors) shouldBeError(validateJP.errors?.[0], "required", schPath, "", requiredMsg("foo"), { missingProperty: "foo", }) const fullValidate = fullAjv.compile(schema) shouldBeValid(fullValidate, data) shouldBeInvalid(fullValidate, invalidData1, 1 + extraErrors) shouldBeError(fullValidate.errors?.[0], "required", schPath, "", requiredMsg("bar"), { missingProperty: "bar", }) shouldBeInvalid(fullValidate, invalidData2, 2 + extraErrors) shouldBeError(fullValidate.errors?.[0], "required", schPath, "", requiredMsg("foo"), { missingProperty: "foo", }) shouldBeError(fullValidate.errors?.[1], "required", schPath, "", requiredMsg("baz"), { missingProperty: "baz", }) } function requiredMsg(prop: string) { return `must have required property '${prop}'` } it('"items" errors should include item index without quotes in instancePath (#48)', () => { const schema1 = { $id: "schema1", type: "array", items: { type: "integer", minimum: 10, }, } const data = [10, 11, 12], invalidData1 = [1, 10], invalidData2 = [10, 9, 11, 8, 12] let validate = ajv.compile(schema1) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData1) shouldBeError(validate.errors?.[0], "minimum", "#/items/minimum", "/0", "must be >= 10") shouldBeInvalid(validate, invalidData2) shouldBeError(validate.errors?.[0], "minimum", "#/items/minimum", "/1", "must be >= 10") const validateJP = ajvJP.compile(schema1) shouldBeValid(validateJP, data) shouldBeInvalid(validateJP, invalidData1) shouldBeError(validateJP.errors?.[0], "minimum", "#/items/minimum", "/0", "must be >= 10") shouldBeInvalid(validateJP, invalidData2) shouldBeError(validateJP.errors?.[0], "minimum", "#/items/minimum", "/1", "must be >= 10") const fullValidate = fullAjv.compile(schema1) shouldBeValid(fullValidate, data) shouldBeInvalid(fullValidate, invalidData1) shouldBeError(fullValidate.errors?.[0], "minimum", "#/items/minimum", "[0]", "must be >= 10") shouldBeInvalid(fullValidate, invalidData2, 2) shouldBeError(fullValidate.errors?.[0], "minimum", "#/items/minimum", "[1]", "must be >= 10") shouldBeError(fullValidate.errors?.[1], "minimum", "#/items/minimum", "[3]", "must be >= 10") const schema2 = { $id: "schema2", type: "array", items: [{minimum: 10}, {minimum: 9}, {minimum: 12}], } validate = ajv.compile(schema2) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData1) shouldBeError(validate.errors?.[0], "minimum", "#/items/0/minimum", "/0", "must be >= 10") shouldBeInvalid(validate, invalidData2) shouldBeError(validate.errors?.[0], "minimum", "#/items/2/minimum", "/2", "must be >= 12") }) it("should have correct schema path for additionalItems", () => { const schema = { type: "array", items: [{type: "integer"}, {type: "integer"}], minItems: 2, additionalItems: false, } const data = [1, 2] const invalidData = [1, 2, 3] test(ajv) test(ajvJP) test(fullAjv) function test(_ajv) { const validate = _ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData) shouldBeError( validate.errors?.[0], "additionalItems", "#/additionalItems", "", "must NOT have more than 2 items" ) } }) describe('"propertyNames" errors', () => { it("should add propertyName to errors", () => { const schema = { type: "object", propertyNames: {pattern: "bar"}, } const data = { bar: {}, "bar.baz@email.example.com": {}, } const invalidData = { bar: {}, "bar.baz@email.example.com": {}, foo: {}, quux: {}, } test(ajv, 2) test(ajvJP, 2) test(fullAjv, 4) function test(_ajv: Ajv, numErrors: number) { const validate = _ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData, numErrors) shouldBeError( validate.errors?.[0], "pattern", "#/propertyNames/pattern", "", 'must match pattern "bar"' ) shouldBeError( validate.errors?.[1], "propertyNames", "#/propertyNames", "", "property name must be valid" ) if (numErrors === 4) { shouldBeError( validate.errors?.[2], "pattern", "#/propertyNames/pattern", "", 'must match pattern "bar"' ) shouldBeError( validate.errors?.[3], "propertyNames", "#/propertyNames", "", "property name must be valid" ) } } }) }) describe("oneOf errors", () => { it("should have errors from inner schemas", () => { const schema = { oneOf: [{type: "number"}, {type: "integer"}], } test(ajv) test(fullAjv) function test(_ajv) { const validate = _ajv.compile(schema) validate("foo").should.equal(false) validate.errors.length.should.equal(3) validate(1).should.equal(false) validate.errors.length.should.equal(1) validate(1.5).should.equal(true) } }) it("should return passing schemas in error params", () => { const schema = { oneOf: [{type: "number"}, {type: "integer"}, {const: 1.5}], } test(ajv) test(fullAjv) function test(_ajv) { const validate = _ajv.compile(schema) validate(1).should.equal(false) let err = validate.errors.pop() err.keyword.should.equal("oneOf") err.params.should.eql({passingSchemas: [0, 1]}) validate(1.5).should.equal(false) err = validate.errors.pop() err.keyword.should.equal("oneOf") err.params.should.eql({passingSchemas: [0, 2]}) validate(2.5).should.equal(true) validate("foo").should.equal(false) err = validate.errors.pop() err.keyword.should.equal("oneOf") err.params.should.eql({passingSchemas: null}) } }) }) describe("anyOf errors", () => { it("should have errors from inner schemas", () => { const schema = { anyOf: [{type: "number"}, {type: "integer"}], } test(ajv) test(fullAjv) function test(_ajv) { const validate = _ajv.compile(schema) validate("foo").should.equal(false) validate.errors.length.should.equal(3) validate(1).should.equal(true) validate(1.5).should.equal(true) } }) }) describe("type errors", () => { describe("integer", () => { it("should have only one error in {allErrors: false} mode", () => { test(ajv) }) it("should return all errors in {allErrors: true} mode", () => { test(fullAjv, 2) }) function test(_ajv, numErrors?: number) { const schema = { type: "integer", minimum: 5, } const validate = _ajv.compile(schema) shouldBeValid(validate, 5) shouldBeInvalid(validate, 5.5) shouldBeInvalid(validate, 4) shouldBeInvalid(validate, "4") shouldBeInvalid(validate, 4.5, numErrors) } }) describe("keyword for another type", () => { it("should have only one error in {allErrors: false} mode", () => { test(ajv) }) it("should return all errors in {allErrors: true} mode", () => { test(fullAjv, 2) }) function test(_ajv, numErrors?: number) { const schema = { type: "array", minItems: 2, minimum: 5, } const validate = _ajv.compile(schema) shouldBeValid(validate, [1, 2]) shouldBeInvalid(validate, [1]) shouldBeInvalid(validate, 5) shouldBeInvalid(validate, 4, numErrors) } }) describe("array of types", () => { it("should have only one error in {allErrors: false} mode", () => { test(ajv) }) it("should return all errors in {allErrors: true} mode", () => { test(fullAjv, 2) }) function test(_ajv: Ajv, numErrors?: number) { const schema = { type: ["array", "object"], minItems: 2, minProperties: 2, minimum: 5, // this keyword would log/throw in strictTypes mode } const validate = _ajv.compile(schema) shouldBeValid(validate, [1, 2]) shouldBeValid(validate, {foo: 1, bar: 2}) shouldBeInvalid(validate, [1]) shouldBeInvalid(validate, {foo: 1}) shouldBeInvalid(validate, 5) // fails because number not allowed shouldBeInvalid(validate, 4, numErrors) } }) }) describe("exclusiveMaximum/Minimum errors", () => { it("should include limits in error message", () => { const schema = { type: "integer", exclusiveMinimum: 2, exclusiveMaximum: 5, } ;[ajv, fullAjv].forEach((_ajv) => { const validate = _ajv.compile(schema) shouldBeValid(validate, 3) shouldBeValid(validate, 4) shouldBeInvalid(validate, 2) testError("exclusiveMinimum", "must be > 2", { comparison: ">", limit: 2, }) shouldBeInvalid(validate, 5) testError("exclusiveMaximum", "must be < 5", { comparison: "<", limit: 5, }) function testError(keyword, message, params) { const err = validate.errors?.[0] shouldBeError(err, keyword, "#/" + keyword, "", message, params) } }) }) it("should include limits in error message with $data", () => { const schema = { type: "object", properties: { smaller: { type: "number", exclusiveMaximum: {$data: "1/larger"}, }, larger: {type: "number"}, }, } ajv = new _Ajv({$data: true}) fullAjv = new _Ajv({ $data: true, allErrors: true, verbose: true, jsPropertySyntax: true, // deprecated logger: false, }) ;[ajv, fullAjv].forEach((_ajv) => { const validate = _ajv.compile(schema) shouldBeValid(validate, {smaller: 2, larger: 4}) shouldBeValid(validate, {smaller: 3, larger: 4}) shouldBeInvalid(validate, {smaller: 4, larger: 4}) testError() shouldBeInvalid(validate, {smaller: 5, larger: 4}) testError() function testError() { const err = validate.errors?.[0] shouldBeError( err, "exclusiveMaximum", "#/properties/smaller/exclusiveMaximum", _ajv.opts.jsPropertySyntax ? ".smaller" : "/smaller", "must be < 4", {comparison: "<", limit: 4} ) } }) }) }) describe("if/then/else errors", () => { let validate: ValidateFunction, numErrors it("if/then/else should include failing keyword in message and params", () => { const schema = { type: "number", if: {maximum: 10}, then: {multipleOf: 2}, else: {multipleOf: 5}, } ;[ajv, fullAjv].forEach((_ajv) => { prepareTest(_ajv, schema) shouldBeValid(validate, 8) shouldBeValid(validate, 15) shouldBeInvalid(validate, 7, numErrors) testIfError("then", 2) shouldBeInvalid(validate, 17, numErrors) testIfError("else", 5) }) }) it("if/then should include failing keyword in message and params", () => { const schema = { type: "number", if: {maximum: 10}, then: {multipleOf: 2}, } ;[ajv, fullAjv].forEach((_ajv) => { prepareTest(_ajv, schema) shouldBeValid(validate, 8) shouldBeValid(validate, 11) shouldBeValid(validate, 12) shouldBeInvalid(validate, 7, numErrors) testIfError("then", 2) }) }) it("if/else should include failing keyword in message and params", () => { const schema = { type: "number", if: {maximum: 10}, else: {multipleOf: 5}, } ;[ajv, fullAjv].forEach((_ajv) => { prepareTest(_ajv, schema) shouldBeValid(validate, 7) shouldBeValid(validate, 8) shouldBeValid(validate, 15) shouldBeInvalid(validate, 17, numErrors) testIfError("else", 5) }) }) function prepareTest(_ajv: Ajv, schema) { validate = _ajv.compile(schema) numErrors = _ajv.opts.allErrors ? 2 : 1 } function testIfError(ifClause, multipleOf) { let err = validate.errors?.[0] shouldBeError( err, "multipleOf", "#/" + ifClause + "/multipleOf", "", "must be multiple of " + multipleOf, {multipleOf: multipleOf} ) if (numErrors === 2) { err = validate.errors?.[1] shouldBeError(err, "if", "#/if", "", 'must match "' + ifClause + '" schema', { failingKeyword: ifClause, }) } } }) describe("uniqueItems errors", () => { it("should not return uniqueItems error when non-unique items are of a different type than required", () => { const schema = { type: "array", items: {type: "number"}, uniqueItems: true, } ;[ajvJP, fullAjv].forEach((_ajv) => { const validate = _ajv.compile(schema) shouldBeValid(validate, [1, 2, 3]) shouldBeInvalid(validate, [1, 2, 2]) shouldBeError( validate.errors?.[0], "uniqueItems", "#/uniqueItems", "", "must NOT have duplicate items (items ## 2 and 1 are identical)", {i: 1, j: 2} ) const expectedErrors = _ajv.opts.allErrors ? 2 : 1 shouldBeInvalid(validate, [1, "2", "2", 2], expectedErrors) testTypeError(0, _ajv.opts.jsPropertySyntax ? "[1]" : "/1") if (expectedErrors === 2) testTypeError(1, _ajv.opts.jsPropertySyntax ? "[2]" : "/2") function testTypeError(i, instancePath) { const err = validate.errors?.[i] shouldBeError(err, "type", "#/items/type", instancePath, "must be number") } }) }) }) function testSchema1(schema, schemaPathPrefix = "#/properties/foo") { _testSchema1(ajv, schema, schemaPathPrefix) _testSchema1(ajvJP, schema, schemaPathPrefix) _testSchema1(fullAjv, schema, schemaPathPrefix) } function _testSchema1(_ajv, schema, schemaPathPrefix) { const schPath = schemaPathPrefix + "/type" const data = {foo: 1}, invalidData = {foo: "bar"} const validate = _ajv.compile(schema) shouldBeValid(validate, data) shouldBeInvalid(validate, invalidData) shouldBeError( validate.errors?.[0], "type", schPath, _ajv.opts.jsPropertySyntax ? ".foo" : "/foo" ) } function shouldBeValid(validate, data) { validate(data).should.equal(true) should.equal(validate.errors, null) } function shouldBeInvalid(validate, data, numErrors = 1) { validate(data).should.equal(false) should.equal(validate.errors.length, numErrors) } function shouldBeError( error, keyword, schemaPath, instancePath, message?: string, params?: Record ) { error.keyword.should.equal(keyword) error.schemaPath.should.equal(schemaPath) error.instancePath.should.equal(instancePath) error.message.should.be.a("string") if (message !== undefined) error.message.should.equal(message) if (params !== undefined) error.params.should.eql(params) } }) ================================================ FILE: spec/extras/$data/absolute_ref.json ================================================ [ { "description": "property is equal to another property [absolute JSON pointer]", "schema": { "properties": { "sameAs": { "const": { "$data": "/thisOne" } }, "thisOne": {} } }, "tests": [ { "description": "same value is valid", "data": { "sameAs": 5, "thisOne": 5 }, "valid": true }, { "description": "same object is valid", "data": { "sameAs": { "foo": 1, "bar": 2 }, "thisOne": { "bar": 2, "foo": 1 } }, "valid": true }, { "description": "another value is invalid", "data": { "sameAs": { "foo": 1 }, "thisOne": { "foo": 2 } }, "valid": false }, { "description": "another type is invalid", "data": { "sameAs": 5, "thisOne": "5" }, "valid": false } ] }, { "description": "items in one array are equal to items in another (limited length) [absolute JSON pointer]", "schema": { "properties": { "arr": { "items": [{}, {}, {}], "additionalItems": false }, "sameArr": { "items": [ { "const": { "$data": "/arr/0" } }, { "const": { "$data": "/arr/1" } }, { "const": { "$data": "/arr/2" } } ], "additionalItems": false } } }, "tests": [ { "description": "equal arrays are valid", "data": { "arr": [ 1, "abc", { "foo": "bar" } ], "sameArr": [ 1, "abc", { "foo": "bar" } ] }, "valid": true }, { "description": "different arrays are invalid", "data": { "arr": [ 1, "abc", { "foo": "bar" } ], "sameArr": [ 1, "abc", { "foo": "foo" } ] }, "valid": false } ] }, { "description": "property value is contained in array [absolute JSON pointer]", "schema": { "properties": { "name": { "type": "string" }, "list": { "type": "array", "contains": { "const": { "$data": "/name" } } } } }, "tests": [ { "description": "1 item array containing property is valid", "data": { "name": "foo", "list": ["foo"] }, "valid": true }, { "description": "2 item array containing property is valid", "data": { "name": "foo", "list": ["foo", "bar"] }, "valid": true }, { "description": "array not containing property is invalid", "data": { "name": "foo", "list": ["bar"] }, "valid": false }, { "description": "empty array is invalid", "data": { "name": "foo", "list": [] }, "valid": false } ] }, { "description": "property is one of values in another property [absolute JSON pointer]", "schema": { "properties": { "allowedValues": {}, "value": { "enum": { "$data": "/allowedValues" } } } }, "tests": [ { "description": "one of the enum is valid", "data": { "allowedValues": [1, 2, 3], "value": 1 }, "valid": true }, { "description": "something else is invalid", "data": { "allowedValues": [1, 2, 3], "value": 4 }, "valid": false }, { "description": "heterogeneous enum validation", "data": { "allowedValues": [ 6, "foo", [], true, { "foo": 12 } ], "value": { "foo": 12 } }, "valid": true }, { "description": "fail if value of enum is not an array", "data": { "allowedValues": "[1, 2, 3]", "value": 1 }, "valid": false }, { "description": "valid if value of enum is undefined", "data": { "value": 1 }, "valid": true } ] }, { "description": "enum in properties [absolute JSON pointer]", "schema": { "properties": { "allowedValues": {} }, "additionalProperties": { "enum": { "$data": "/allowedValues" } } }, "tests": [ { "description": "properties are valid", "data": { "allowedValues": ["foo", "bar"], "a": "foo", "b": "bar" }, "valid": true }, { "description": "properties are invalid", "data": { "allowedValues": ["foo", "bar"], "a": "foo", "b": "baz" }, "valid": false } ] }, { "description": "required schema in data property [absolute JSON pointer]", "schema": { "properties": { "foo": {}, "bar": {}, "requiredProperties": {} }, "required": { "$data": "/requiredProperties" } }, "tests": [ { "description": "present required property is valid", "data": { "foo": 1, "requiredProperties": ["foo"] }, "valid": true }, { "description": "non-present required property is invalid", "data": { "bar": 2, "requiredProperties": ["foo"] }, "valid": false }, { "description": "non-present second required property is invalid", "data": { "foo": 1, "requiredProperties": ["foo", "bar"] }, "valid": false }, { "description": "two present required properties is valid", "data": { "foo": 1, "bar": 2, "requiredProperties": ["foo", "bar"] }, "valid": true }, { "description": "fails if value of required is not an array", "data": { "foo": 1, "bar": 2, "requiredProperties": true }, "valid": false }, { "description": "valid if value of required is undefined", "data": { "foo": 1, "bar": 2 }, "valid": true } ] }, { "description": "absolute JSON pointer can access data outside of a $ref", "schema": { "definitions": { "baz": { "type": "object", "properties": { "foo": { "const": { "$data": "/bar" } }, "bar": {} } } }, "properties": { "foo": { "const": { "$data": "/bar" } }, "bar": {}, "baz": { "$ref": "#/definitions/baz" } } }, "tests": [ { "description": "$data reference with absolute JSON pointer resolves from root of data", "data": { "foo": 1, "bar": 1, "baz": { "foo": 1, "bar": 2 } }, "valid": true }, { "description": "$data reference with absolute JSON pointer should NOT resolve to root of $ref", "data": { "foo": 2, "bar": 2, "baz": { "foo": 1, "bar": 1 } }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/const.json ================================================ [ { "description": "property is equal to another property", "schema": { "properties": { "sameAs": {"const": {"$data": "1/thisOne"}}, "thisOne": {} } }, "tests": [ { "description": "same value is valid", "data": { "sameAs": 5, "thisOne": 5 }, "valid": true }, { "description": "same object is valid", "data": { "sameAs": {"foo": 1, "bar": 2}, "thisOne": {"bar": 2, "foo": 1} }, "valid": true }, { "description": "another value is invalid", "data": { "sameAs": {"foo": 1}, "thisOne": {"foo": 2} }, "valid": false }, { "description": "another type is invalid", "data": { "sameAs": 5, "thisOne": "5" }, "valid": false }, { "description": "valid when another property ('const') not defined", "data": { "sameAs": 5 }, "valid": true } ] }, { "description": "property values are equal to property names", "schema": { "additionalProperties": { "const": {"$data": "0#"} } }, "tests": [ { "description": "valid object", "data": {"foo": "foo", "bar": "bar", "baz": "baz"}, "valid": true }, { "description": "invalid object", "data": {"foo": "bar"}, "valid": false } ] }, { "description": "items are equal to their indeces", "schema": { "items": { "const": {"$data": "0#"} } }, "tests": [ { "description": "valid array", "data": [0, 1, 2, 3], "valid": true }, { "description": "invalid array", "data": [0, 2], "valid": false } ] }, { "description": "items in one array are equal to items in another (limited length)", "schema": { "properties": { "arr": { "items": [{}, {}, {}], "additionalItems": false }, "sameArr": { "items": [ {"const": {"$data": "2/arr/0"}}, {"const": {"$data": "2/arr/1"}}, {"const": {"$data": "2/arr/2"}} ], "additionalItems": false } } }, "tests": [ { "description": "equal arrays are valid", "data": { "arr": [1, "abc", {"foo": "bar"}], "sameArr": [1, "abc", {"foo": "bar"}] }, "valid": true }, { "description": "different arrays are invalid", "data": { "arr": [1, "abc", {"foo": "bar"}], "sameArr": [1, "abc", {"foo": "foo"}] }, "valid": false } ] }, { "description": "any data is equal to itself", "schema": { "const": {"$data": "0"} }, "tests": [ { "description": "number is equal to itself", "data": 1, "valid": true }, { "description": "string is equal to itself", "data": "foo", "valid": true }, { "description": "object is equal to itself", "data": {"foo": "bar"}, "valid": true }, { "description": "array is equal to itself", "data": [1, 2, 3], "valid": true } ] }, { "description": "property value is contained in array", "schema": { "properties": { "name": {"type": "string"}, "list": { "type": "array", "contains": {"const": {"$data": "2/name"}} } } }, "tests": [ { "description": "1 item array containing property is valid", "data": { "name": "foo", "list": ["foo"] }, "valid": true }, { "description": "2 item array containing property is valid", "data": { "name": "foo", "list": ["foo", "bar"] }, "valid": true }, { "description": "array not containing property is invalid", "data": { "name": "foo", "list": ["bar"] }, "valid": false }, { "description": "empty array is invalid", "data": { "name": "foo", "list": [] }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/enum.json ================================================ [ { "description": "property is one of values in another property", "schema": { "properties": { "allowedValues": {}, "value": {"enum": {"$data": "1/allowedValues"}} } }, "tests": [ { "description": "one of the enum is valid", "data": { "allowedValues": [1, 2, 3], "value": 1 }, "valid": true }, { "description": "something else is invalid", "data": { "allowedValues": [1, 2, 3], "value": 4 }, "valid": false }, { "description": "heterogeneous enum validation", "data": { "allowedValues": [6, "foo", [], true, {"foo": 12}], "value": {"foo": 12} }, "valid": true }, { "description": "fail if value of enum is not an array", "data": { "allowedValues": "[1, 2, 3]", "value": 1 }, "valid": false }, { "description": "valid if value of enum is undefined", "data": { "value": 1 }, "valid": true } ] }, { "description": "enum in properties", "schema": { "properties": { "allowedValues": {} }, "additionalProperties": { "enum": {"$data": "1/allowedValues"} } }, "tests": [ { "description": "properties are valid", "data": { "allowedValues": ["foo", "bar"], "a": "foo", "b": "bar" }, "valid": true }, { "description": "properties are invalid", "data": { "allowedValues": ["foo", "bar"], "a": "foo", "b": "baz" }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/exclusiveMaximum.json ================================================ [ { "description": "one property is exclusiveMaximum for another", "schema": { "properties": { "larger": {}, "smaller": { "exclusiveMaximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the exclusiveMaximum is valid", "data": { "larger": 3, "smaller": 2 }, "valid": true }, { "description": "equal to the exclusiveMaximum is invalid", "data": { "larger": 3, "smaller": 3 }, "valid": false }, { "description": "above the exclusiveMaximum is invalid", "data": { "larger": 3, "smaller": 4 }, "valid": false }, { "description": "ignores non-numbers", "data": { "larger": 3, "smaller": "4" }, "valid": true }, { "description": "fails if value of exclusiveMaximum is not number", "data": { "larger": "3", "smaller": 2 }, "valid": false }, { "description": "valid if value of exclusiveMaximum is undefined", "data": { "smaller": 2 }, "valid": true } ] }, { "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum > maximum", "schema": { "properties": { "larger": {}, "smaller": { "exclusiveMaximum": 3.5, "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the maximum is valid", "data": { "larger": 3, "smaller": 2 }, "valid": true }, { "description": "equal to the maximum is valid", "data": { "larger": 3, "smaller": 3 }, "valid": true }, { "description": "above the maximum is invalid", "data": { "larger": 3, "smaller": 3.2 }, "valid": false } ] }, { "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum = maximum", "schema": { "properties": { "larger": {}, "smaller": { "exclusiveMaximum": 3, "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the maximum is valid", "data": { "larger": 3, "smaller": 2 }, "valid": true }, { "description": "boundary point is invalid", "data": { "larger": 3, "smaller": 3 }, "valid": false }, { "description": "above the maximum is invalid", "data": { "larger": 3, "smaller": 4 }, "valid": false } ] }, { "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum < maximum", "schema": { "properties": { "larger": {}, "smaller": { "exclusiveMaximum": 2.5, "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the exclusiveMaximum is valid", "data": { "larger": 3, "smaller": 2 }, "valid": true }, { "description": "boundary point is invalid", "data": { "larger": 3, "smaller": 2.5 }, "valid": false }, { "description": "above the exclusiveMaximum is invalid", "data": { "larger": 3, "smaller": 2.8 }, "valid": false } ] }, { "description": "exclusiveMaximum and maximum as $data, exclusiveMaximum > maximum", "schema": { "properties": { "larger": {}, "largerExclusive": {}, "smaller": { "exclusiveMaximum": {"$data": "1/largerExclusive"}, "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the maximum is valid", "data": { "larger": 3, "largerExclusive": 3.5, "smaller": 2 }, "valid": true }, { "description": "equal to the maximum is valid", "data": { "larger": 3, "largerExclusive": 3.5, "smaller": 3 }, "valid": true }, { "description": "above the maximum is invalid", "data": { "larger": 3, "largerExclusive": 3.5, "smaller": 3.2 }, "valid": false } ] }, { "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum = maximum", "schema": { "properties": { "larger": {}, "largerExclusive": {}, "smaller": { "exclusiveMaximum": {"$data": "1/largerExclusive"}, "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the maximum is valid", "data": { "larger": 3, "largerExclusive": 3, "smaller": 2 }, "valid": true }, { "description": "boundary point is invalid", "data": { "larger": 3, "largerExclusive": 3, "smaller": 3 }, "valid": false }, { "description": "above the maximum is invalid", "data": { "larger": 3, "largerExclusive": 3, "smaller": 4 }, "valid": false } ] }, { "description": "exclusiveMaximum as number and maximum as $data, exclusiveMaximum < maximum", "schema": { "properties": { "larger": {}, "largerExclusive": {}, "smaller": { "exclusiveMaximum": {"$data": "1/largerExclusive"}, "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the exclusiveMaximum is valid", "data": { "larger": 3, "largerExclusive": 2.5, "smaller": 2 }, "valid": true }, { "description": "boundary point is invalid", "data": { "larger": 3, "largerExclusive": 2.5, "smaller": 2.5 }, "valid": false }, { "description": "above the exclusiveMaximum is invalid", "data": { "larger": 3, "largerExclusive": 2.5, "smaller": 2.8 }, "valid": false } ] }, { "description": "items in array are < than their indeces", "schema": { "items": { "exclusiveMaximum": {"$data": "0#"} } }, "tests": [ { "description": "valid array", "data": ["", 0, 1, 2, 3, 4], "valid": true }, { "description": "invalid array (1=1)", "data": ["", 1], "valid": false } ] } ] ================================================ FILE: spec/extras/$data/exclusiveMinimum.json ================================================ [ { "description": "one property is exclusiveMinimum for another", "schema": { "properties": { "smaller": {}, "larger": { "exclusiveMinimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the exclusiveMinimum is valid", "data": { "smaller": 3, "larger": 4 }, "valid": true }, { "description": "equal to the exclusiveMinimum is invalid", "data": { "smaller": 3, "larger": 3 }, "valid": false }, { "description": "below the exclusiveMinimum is invalid", "data": { "smaller": 3, "larger": 2 }, "valid": false }, { "description": "ignores non-numbers", "data": { "smaller": 3, "larger": "2" }, "valid": true }, { "description": "fails if value of exclusiveMinimum is not number", "data": { "smaller": "3", "larger": 4 }, "valid": false } ] }, { "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum < minimum", "schema": { "properties": { "smaller": {}, "larger": { "exclusiveMinimum": 2.5, "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the minimum is valid", "data": { "smaller": 3, "larger": 4 }, "valid": true }, { "description": "equal to the minimum is valid", "data": { "smaller": 3, "larger": 3 }, "valid": true }, { "description": "below the minimum is invalid", "data": { "smaller": 3, "larger": 2.8 }, "valid": false } ] }, { "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum = minimum", "schema": { "properties": { "smaller": {}, "larger": { "exclusiveMinimum": 3, "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the minimum is valid", "data": { "smaller": 3, "larger": 4 }, "valid": true }, { "description": "boundary point is invalid", "data": { "smaller": 3, "larger": 3 }, "valid": false }, { "description": "below the minimum is invalid", "data": { "smaller": 3, "larger": 2 }, "valid": false } ] }, { "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum > minimum", "schema": { "properties": { "smaller": {}, "larger": { "exclusiveMinimum": 3.5, "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the exclusiveMinimum is valid", "data": { "smaller": 3, "larger": 4 }, "valid": true }, { "description": "boundary point is invalid", "data": { "smaller": 3, "larger": 3.5 }, "valid": false }, { "description": "below the exclusiveMinimum is invalid", "data": { "smaller": 3, "larger": 3.3 }, "valid": false } ] }, { "description": "exclusiveMinimum and minimum as $data, exclusiveMinimum < minimum", "schema": { "properties": { "smaller": {}, "smallerExclusive": {}, "larger": { "exclusiveMinimum": {"$data": "1/smallerExclusive"}, "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the minimum is valid", "data": { "smaller": 3, "smallerExclusive": 2.5, "larger": 4 }, "valid": true }, { "description": "equal to the minimum is valid", "data": { "smaller": 3, "smallerExclusive": 2.5, "larger": 3 }, "valid": true }, { "description": "below the minimum is invalid", "data": { "smaller": 3, "smallerExclusive": 2.5, "larger": 2.8 }, "valid": false } ] }, { "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum = minimum", "schema": { "properties": { "smaller": {}, "smallerExclusive": {}, "larger": { "exclusiveMinimum": {"$data": "1/smallerExclusive"}, "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the minimum is valid", "data": { "smaller": 3, "smallerExclusive": 3, "larger": 4 }, "valid": true }, { "description": "boundary point is invalid", "data": { "smaller": 3, "smallerExclusive": 3, "larger": 3 }, "valid": false }, { "description": "below the minimum is invalid", "data": { "smaller": 3, "smallerExclusive": 3, "larger": 2 }, "valid": false } ] }, { "description": "exclusiveMinimum as number and minimum as $data, exclusiveMinimum > minimum", "schema": { "properties": { "smaller": {}, "smallerExclusive": {}, "larger": { "exclusiveMinimum": {"$data": "1/smallerExclusive"}, "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the exclusiveMinimum is valid", "data": { "smaller": 3, "smallerExclusive": 3.5, "larger": 4 }, "valid": true }, { "description": "boundary point is invalid", "data": { "smaller": 3, "smallerExclusive": 3.5, "larger": 3.5 }, "valid": false }, { "description": "below the exclusiveMinimum is invalid", "data": { "smaller": 3, "smallerExclusive": 3.5, "larger": 3.3 }, "valid": false } ] }, { "description": "items in array are > than their indeces", "schema": { "items": { "exclusiveMinimum": {"$data": "0#"} } }, "tests": [ { "description": "valid array", "data": [1, 2, 3, 4, 5, 6], "valid": true }, { "description": "invalid array (1=1)", "data": [2, 1], "valid": false } ] } ] ================================================ FILE: spec/extras/$data/format.json ================================================ [ { "description": "one property has format set in another property", "schema": { "properties": { "str": { "format": { "$data": "1/strFormat" } }, "strFormat": {} } }, "tests": [ { "description": "allowed unknown format is valid", "data": { "str": "any value", "strFormat": "allowedUnknown" }, "valid": true }, { "description": "unknown format is invalid", "data": { "str": "any value", "strFormat": "completelyUnknown" }, "valid": false }, { "description": "valid if the format is undefined", "data": { "str": "any value" }, "valid": true }, { "description": "fails if value of format is not a string", "data": { "str": "1963-06-19T08:30:06.283185Z", "strFormat": 1963 }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/maxItems.json ================================================ [ { "description": "array length is <= than another property", "schema": { "properties": { "maxArrayLength": {}, "array": {"maxItems": {"$data": "1/maxArrayLength"}} } }, "tests": [ { "description": "shorter is valid", "data": { "maxArrayLength": 2, "array": [1] }, "valid": true }, { "description": "exact length is valid", "data": { "maxArrayLength": 2, "array": [1, 2] }, "valid": true }, { "description": "too long is invalid", "data": { "maxArrayLength": 2, "array": [1, 2, 3] }, "valid": false }, { "description": "ignores non-arrays", "data": { "maxArrayLength": 2, "array": "foobar" }, "valid": true }, { "description": "fails if value of maxItems is not a number", "data": { "maxArrayLength": "2", "array": [1, 2] }, "valid": false }, { "description": "valid if value of maxItems is undefined", "data": { "array": [1, 2] }, "valid": true } ] } ] ================================================ FILE: spec/extras/$data/maxLength.json ================================================ [ { "description": "string length is <= than another property", "schema": { "properties": { "maximumLength": {}, "string": {"maxLength": {"$data": "1/maximumLength"}} } }, "tests": [ { "description": "shorter is valid", "data": { "maximumLength": 2, "string": "f" }, "valid": true }, { "description": "exact length is valid", "data": { "maximumLength": 2, "string": "fo" }, "valid": true }, { "description": "too long is invalid", "data": { "maximumLength": 2, "string": "foo" }, "valid": false }, { "description": "ignores non-strings", "data": { "maximumLength": 2, "string": 100 }, "valid": true }, { "description": "fails if value of maxLength is not a number", "data": { "maximumLength": "2", "string": "f" }, "valid": false }, { "description": "valid if value of maxLength is undefined", "data": { "string": "f" }, "valid": true } ] } ] ================================================ FILE: spec/extras/$data/maxProperties.json ================================================ [ { "description": "number of object properties is <= than another property", "schema": { "properties": { "maxKeys": {}, "object": {"maxProperties": {"$data": "1/maxKeys"}} } }, "tests": [ { "description": "shorter is valid", "data": { "maxKeys": 2, "object": {"foo": 1} }, "valid": true }, { "description": "exact length is valid", "data": { "maxKeys": 2, "object": {"foo": 1, "bar": 2} }, "valid": true }, { "description": "too long is invalid", "data": { "maxKeys": 2, "object": {"foo": 1, "bar": 2, "baz": 3} }, "valid": false }, { "description": "ignores non-objects", "data": { "maxKeys": 2, "object": "foobar" }, "valid": true }, { "description": "fails if value of maxProperties is not a number", "data": { "maxKeys": "2", "object": {"foo": 1, "bar": 2} }, "valid": false }, { "description": "valid if value of maxProperties is undefined", "data": { "object": {"foo": 1, "bar": 2} }, "valid": true } ] } ] ================================================ FILE: spec/extras/$data/maximum.json ================================================ [ { "description": "one property is maximum for another", "schema": { "properties": { "larger": {}, "smallerOrEqual": { "maximum": {"$data": "1/larger"} } } }, "tests": [ { "description": "below the maximum is valid", "data": { "larger": 3, "smallerOrEqual": 2 }, "valid": true }, { "description": "equal to the maximum is valid", "data": { "larger": 3, "smallerOrEqual": 3 }, "valid": true }, { "description": "above the maximum is invalid", "data": { "larger": 3, "smallerOrEqual": 4 }, "valid": false }, { "description": "ignores non-numbers", "data": { "larger": 3, "smallerOrEqual": "4" }, "valid": true }, { "description": "fails if value of maximum is not number", "data": { "larger": "3", "smallerOrEqual": 2 }, "valid": false }, { "description": "valid if value of maximum is undefined", "data": { "smallerOrEqual": 2 }, "valid": true } ] }, { "description": "exclusiveMaximum is $data", "schema": { "properties": { "number": { "maximum": 5, "exclusiveMaximum": {"$data": "1/exclusiveMaximum"} }, "exclusiveMaximum": {} } }, "tests": [ { "description": "exclusiveMaximum boolean no longer supported", "data": { "number": 4, "exclusiveMaximum": true }, "valid": false }, { "description": "below the maximum is valid when exclusiveMaximum is strictly larger", "data": { "number": 4, "exclusiveMaximum": 4.1 }, "valid": true }, { "description": "below the maximum is NOT valid when exclusiveMaximum is equal", "data": { "number": 4, "exclusiveMaximum": 4 }, "valid": false }, { "description": "below the maximum is valid when exclusiveMaximum is undefined", "data": { "number": 4 }, "valid": true }, { "description": "boundary point is invalid when exclusiveMaximum is equal", "data": { "number": 3, "exclusiveMaximum": 3 }, "valid": false }, { "description": "boundary point is valid when exclusiveMaximum is smaller", "data": { "number": 3, "exclusiveMaximum": 3.1 }, "valid": true }, { "description": "boundary point is valid when exclusiveMaximum is undefined", "data": { "number": 3 }, "valid": true }, { "description": "above the maximum is invalid", "data": { "number": 6 }, "valid": false }, { "description": "fails if value of exclusiveMaximum is not number", "data": { "number": 4, "exclusiveMaximum": "5" }, "valid": false } ] }, { "description": "maximum and exclusiveMaximum are $data", "schema": { "properties": { "larger": {}, "smallerOrEqual": { "maximum": {"$data": "1/larger"}, "exclusiveMaximum": {"$data": "1/exclusiveMaximum"} }, "exclusiveMaximum": {} } }, "tests": [ { "description": "exclusiveMaximum boolean no longer supported", "data": { "larger": 3, "smallerOrEqual": 2, "exclusiveMaximum": true }, "valid": false }, { "description": "below the maximum is valid when exclusiveMaximum is strictly larger", "data": { "larger": 3, "smallerOrEqual": 2, "exclusiveMaximum": 2.1 }, "valid": true }, { "description": "below the maximum is NOT valid when exclusiveMaximum is equal", "data": { "larger": 3, "smallerOrEqual": 2, "exclusiveMaximum": 2 }, "valid": false }, { "description": "below the maximum is valid when exclusiveMaximum is undefined", "data": { "larger": 3, "smallerOrEqual": 2 }, "valid": true }, { "description": "above the maximum is invalid", "data": { "larger": 3, "smallerOrEqual": 4, "exclusiveMaximum": 5 }, "valid": false }, { "description": "above the maximum is invalid when exclusiveMaximum is undefined", "data": { "larger": 3, "smallerOrEqual": 4 }, "valid": false }, { "description": "fails if value of exclusiveMaximum is not number", "data": { "larger": 3, "smallerOrEqual": 2, "exclusiveMaximum": "5" }, "valid": false }, { "description": "boundary point is valid when exclusiveMaximum is strictly larger", "data": { "larger": 3, "smallerOrEqual": 3, "exclusiveMaximum": 3.1 }, "valid": true }, { "description": "boundary point is invalid when exclusiveMaximum is equal", "data": { "larger": 3, "smallerOrEqual": 3, "exclusiveMaximum": 3 }, "valid": false }, { "description": "boundary point is valid when exclusiveMaximum is undefined", "data": { "larger": 3, "smallerOrEqual": 3 }, "valid": true } ] }, { "description": "items in array are <= than their indeces", "schema": { "items": { "maximum": {"$data": "0#"} } }, "tests": [ { "description": "valid array", "data": [0, 1, 1, 2, 2, 4], "valid": true }, { "description": "invalid array (2>1)", "data": [0, 2], "valid": false } ] } ] ================================================ FILE: spec/extras/$data/minItems.json ================================================ [ { "description": "array length is >= than another property", "schema": { "properties": { "minArrayLength": {}, "array": {"minItems": {"$data": "1/minArrayLength"}} } }, "tests": [ { "description": "longer is valid", "data": { "minArrayLength": 2, "array": [1, 2, 3] }, "valid": true }, { "description": "exact length is valid", "data": { "minArrayLength": 2, "array": [1, 2] }, "valid": true }, { "description": "too short is invalid", "data": { "minArrayLength": 2, "array": [1] }, "valid": false }, { "description": "ignores non-arrays", "data": { "minArrayLength": 2, "array": "foobar" }, "valid": true }, { "description": "fails if value of minItems is not a number", "data": { "minArrayLength": "2", "array": [1, 2] }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/minLength.json ================================================ [ { "description": "string length is >= than another property", "schema": { "properties": { "minimumLength": {}, "string": {"minLength": {"$data": "1/minimumLength"}} } }, "tests": [ { "description": "longer is valid", "data": { "minimumLength": 2, "string": "foo" }, "valid": true }, { "description": "exact length is valid", "data": { "minimumLength": 2, "string": "fo" }, "valid": true }, { "description": "too short is invalid", "data": { "minimumLength": 2, "string": "f" }, "valid": false }, { "description": "ignores non-strings", "data": { "minimumLength": 2, "string": 100 }, "valid": true }, { "description": "fails if value of minLength is not a number", "data": { "minimumLength": "2", "string": "foo" }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/minProperties.json ================================================ [ { "description": "number of object properties is >= than another property", "schema": { "properties": { "minKeys": {}, "object": {"minProperties": {"$data": "1/minKeys"}} } }, "tests": [ { "description": "longer is valid", "data": { "minKeys": 2, "object": {"foo": 1, "bar": 2, "baz": 3} }, "valid": true }, { "description": "exact length is valid", "data": { "minKeys": 2, "object": {"foo": 1, "bar": 2} }, "valid": true }, { "description": "too short is invalid", "data": { "minKeys": 2, "object": {"foo": 1} }, "valid": false }, { "description": "ignores non-objects", "data": { "minKeys": 2, "object": "foobar" }, "valid": true }, { "description": "fails if value of minProperties is not a number", "data": { "minKeys": "2", "object": {"foo": 1, "bar": 2} }, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/minimum.json ================================================ [ { "description": "one property is minimum for another", "schema": { "properties": { "smaller": {}, "largerOrEqual": { "minimum": {"$data": "1/smaller"} } } }, "tests": [ { "description": "above the minimum is valid", "data": { "smaller": 3, "largerOrEqual": 4 }, "valid": true }, { "description": "equal to the minimum is valid", "data": { "smaller": 3, "largerOrEqual": 3 }, "valid": true }, { "description": "below the minimum is invalid", "data": { "smaller": 3, "largerOrEqual": 2 }, "valid": false }, { "description": "ignores non-numbers", "data": { "smaller": 3, "largerOrEqual": "2" }, "valid": true }, { "description": "fails if value of minimum is not number", "data": { "smaller": "3", "largerOrEqual": 4 }, "valid": false } ] }, { "description": "exclusiveMinimum is $data", "schema": { "properties": { "number": { "minimum": 3, "exclusiveMinimum": {"$data": "1/exclusiveMinimum"} }, "exclusiveMinimum": {} } }, "tests": [ { "description": "exclusiveMinimum boolean no longer supported", "data": { "number": 4, "exclusiveMinimum": true }, "valid": false }, { "description": "above the minimum is valid when exclusiveMinimum is strictly smaller", "data": { "number": 4, "exclusiveMinimum": 3.9 }, "valid": true }, { "description": "above the minimum is NOT valid when exclusiveMinimum is equal", "data": { "number": 4, "exclusiveMinimum": 4 }, "valid": false }, { "description": "above the minimum is valid when exclusiveMinimum is undefined", "data": { "number": 4 }, "valid": true }, { "description": "boundary point is invalid when exclusiveMinimum is equal", "data": { "number": 3, "exclusiveMinimum": 3 }, "valid": false }, { "description": "boundary point is valid when exclusiveMinimum is smaller", "data": { "number": 3, "exclusiveMinimum": 2.9 }, "valid": true }, { "description": "boundary point is valid when exclusiveMinimum is undefined", "data": { "number": 3 }, "valid": true }, { "description": "below the minimum is invalid", "data": { "number": 2 }, "valid": false }, { "description": "fails if value of exclusiveMinimum is not number", "data": { "number": 4, "exclusiveMinimum": "3" }, "valid": false } ] }, { "description": "minimum and exclusiveMinimum are $data", "schema": { "properties": { "smaller": {}, "largerOrEqual": { "minimum": {"$data": "1/smaller"}, "exclusiveMinimum": {"$data": "1/exclusiveMinimum"} }, "exclusiveMinimum": {} } }, "tests": [ { "description": "exclusiveMinimum boolean no longer supported", "data": { "smaller": 3, "largerOrEqual": 4, "exclusiveMinimum": true }, "valid": false }, { "description": "above the minimum is valid when exclusiveMinimum is strictly smaller", "data": { "smaller": 3, "largerOrEqual": 4, "exclusiveMinimum": 3.9 }, "valid": true }, { "description": "above the minimum is NOT valid when exclusiveMinimum is equal", "data": { "smaller": 3, "largerOrEqual": 4, "exclusiveMinimum": 4 }, "valid": false }, { "description": "above the minimum is valid when exclusiveMinimum is undefined", "data": { "smaller": 3, "largerOrEqual": 4 }, "valid": true }, { "description": "below the minimum is invalid", "data": { "smaller": 3, "largerOrEqual": 2, "exclusiveMinimum": 1.5 }, "valid": false }, { "description": "below the minimum is invalid when exclusiveMinimum is undefined", "data": { "smaller": 3, "largerOrEqual": 2 }, "valid": false }, { "description": "fails if value of exclusiveMinimum is not number", "data": { "smaller": 3, "largerOrEqual": 4, "exclusiveMinimum": "3" }, "valid": false }, { "description": "boundary point is valid when exclusiveMinimum is strictly smaller", "data": { "smaller": 3, "largerOrEqual": 3, "exclusiveMinimum": 2.9 }, "valid": true }, { "description": "boundary point is invalid when exclusiveMinimum is equal", "data": { "smaller": 3, "largerOrEqual": 3, "exclusiveMinimum": 3 }, "valid": false }, { "description": "boundary point is valid when exclusiveMinimum is undefined", "data": { "smaller": 3, "largerOrEqual": 3 }, "valid": true } ] }, { "description": "items in array are >= than their indeces", "schema": { "items": { "minimum": {"$data": "0#"} } }, "tests": [ { "description": "valid array", "data": [1, 2, 2, 4, 5, 5], "valid": true }, { "description": "invalid array (0.5<1)", "data": [0, 0.5], "valid": false } ] } ] ================================================ FILE: spec/extras/$data/multipleOf.json ================================================ [ { "description": "one property is multiple of another", "schema": { "properties": { "divider": {}, "multiple": {"multipleOf": {"$data": "1/divider"}} } }, "tests": [ { "description": "int by int valid", "data": { "divider": 3, "multiple": 12 }, "valid": true }, { "description": "float by float valid", "data": { "divider": 2.5, "multiple": 7.5 }, "valid": true }, { "description": "int by int invalid", "data": { "divider": 3, "multiple": 10 }, "valid": false }, { "description": "float by float invalid", "data": { "divider": 2.5, "multiple": 8 }, "valid": false }, { "description": "ignores non-numbers", "data": { "divider": 2.5, "multiple": "not a number" }, "valid": true }, { "description": "fails if value of multipleOf is not a number", "data": { "divider": "2.5", "multiple": 10 }, "valid": false }, { "description": "valid if value of multipleOf is undefined", "data": { "multiple": 10 }, "valid": true }, { "description": "invalid if value of multipleOf is 0", "data": { "divider": 0, "multiple": 10 }, "valid": false } ] }, { "description": "one property is multiple of another property with escaped characters", "schema": { "properties": { "/divider~": {"type": "number"}, "/multiple~": {"multipleOf": {"$data": "1/~1divider~0"}} } }, "tests": [ { "description": "int by int valid", "data": { "/divider~": 3, "/multiple~": 12 }, "valid": true }, { "description": "int by int invalid", "data": { "/divider~": 3, "/multiple~": 10 }, "valid": false } ] }, { "description": "one subproperty is multiple of another", "schema": { "properties": { "divider": { "properties": { "value": {"type": "number"} } }, "multiple": { "properties": { "value": {"multipleOf": {"$data": "2/divider/value"}} } } } }, "tests": [ { "description": "int by int valid", "data": { "divider": {"value": 3}, "multiple": {"value": 12} }, "valid": true }, { "description": "int by int invalid", "data": { "divider": {"value": 3}, "multiple": {"value": 10} }, "valid": false } ] }, { "description": "item is a multiple of its index", "schema": { "items": [{}], "additionalItems": { "multipleOf": {"$data": "0#"} } }, "tests": [ { "description": "valid array", "data": ["anything", 1, 4, 12, 8, 10], "valid": true }, { "description": "invalid array (3 is not a multiple of 2)", "data": ["anything", 1, 3], "valid": false } ] }, { "description": "item property is a multiple of item index", "schema": { "items": [{}], "additionalItems": { "properties": { "value": { "multipleOf": {"$data": "1#"} } } } }, "tests": [ { "description": "valid array", "data": ["anything", {"value": 1}, {"value": 4}, {"value": 12}], "valid": true }, { "description": "invalid array (3 is not a multiple of 2)", "data": ["anything", {"value": 1}, {"value": 3}], "valid": false } ] } ] ================================================ FILE: spec/extras/$data/pattern.json ================================================ [ { "description": "one property is pattern for another", "schema": { "properties": { "shouldMatch": {}, "string": {"pattern": {"$data": "1/shouldMatch"}} } }, "tests": [ { "description": "a matching pattern is valid", "data": { "shouldMatch": "^a*$", "string": "aaa" }, "valid": true }, { "description": "a non-matching pattern is invalid", "data": { "shouldMatch": "^a*$", "string": "abc" }, "valid": false }, { "description": "ignores non-strings", "data": { "shouldMatch": "^a*$", "string": 123 }, "valid": true }, { "description": "fails if value of pattern is not a string", "data": { "shouldMatch": 123, "string": "123" }, "valid": false }, { "description": "valid if value of pattern is undefined", "data": { "string": "123" }, "valid": true } ] }, { "description": "property values should contain their names", "schema": { "additionalProperties": { "pattern": {"$data": "0#"} } }, "tests": [ { "description": "valid property values", "data": {"foo": "1foo", "bar": "bar2", "baz": "3baz4"}, "valid": true }, { "description": "invalid property values", "data": {"foo": "fo"}, "valid": false } ] } ] ================================================ FILE: spec/extras/$data/required.json ================================================ [ { "description": "required schema in data property", "schema": { "properties": { "foo": {}, "bar": {}, "requiredProperties": {} }, "required": {"$data": "0/requiredProperties"} }, "tests": [ { "description": "present required property is valid", "data": { "foo": 1, "requiredProperties": ["foo"] }, "valid": true }, { "description": "non-present required property is invalid", "data": { "bar": 2, "requiredProperties": ["foo"] }, "valid": false }, { "description": "non-present second required property is invalid", "data": { "foo": 1, "requiredProperties": ["foo", "bar"] }, "valid": false }, { "description": "two present required properties is valid", "data": { "foo": 1, "bar": 2, "requiredProperties": ["foo", "bar"] }, "valid": true }, { "description": "fails if value of required is not an array", "data": { "foo": 1, "bar": 2, "requiredProperties": true }, "valid": false }, { "description": "valid if value of required is undefined", "data": { "foo": 1, "bar": 2 }, "valid": true } ] } ] ================================================ FILE: spec/extras/$data/uniqueItems.json ================================================ [ { "description": "uniqueItems in property", "schema": { "properties": { "list": { "uniqueItems": {"$data": "1/unique"} }, "unique": {} } }, "tests": [ { "description": "unique array is valid", "data": { "list": [1, 2], "unique": true }, "valid": true }, { "description": "non-unique array is invalid", "data": { "list": [1, 1], "unique": true }, "valid": false }, { "description": "non-unique array is valid if uniqueItems is false", "data": { "list": [1, 1], "unique": false }, "valid": true }, { "description": "non-unique array is valid if uniqueItems is undefined", "data": { "list": [1, 1] }, "valid": true }, { "description": "fails if uniqueItems is not boolean", "data": { "list": [1, 2], "unique": "true" }, "valid": false } ] } ] ================================================ FILE: spec/extras/const.json ================================================ [ { "description": "const keyword requires the value to be equal to some constant", "schema": {"const": 2}, "tests": [ { "description": "same value is valid", "data": 2, "valid": true }, { "description": "another value is invalid", "data": 5, "valid": false }, { "description": "another type is invalid", "data": "a", "valid": false } ] }, { "description": "const keyword requires the value to be equal to some object", "schema": {"const": {"foo": "bar", "baz": "bax"}}, "tests": [ { "description": "same object is valid", "data": {"foo": "bar", "baz": "bax"}, "valid": true }, { "description": "same object with different property order is valid", "data": {"baz": "bax", "foo": "bar"}, "valid": true }, { "description": "another object is invalid", "data": {"foo": "bar"}, "valid": false }, { "description": "another type is invalid", "data": [1, 2], "valid": false } ] }, { "description": "const keyword with null", "schema": {"const": null}, "tests": [ { "description": "null is valid", "data": null, "valid": true }, { "description": "not null is invalid", "data": 0, "valid": false } ] } ] ================================================ FILE: spec/extras/contains.json ================================================ [ { "description": "contains keyword requires the item matching schema to be present", "schema": { "contains": {"minimum": 5} }, "tests": [ { "description": "array with item matching schema (5) is valid", "data": [3, 4, 5], "valid": true }, { "description": "array with item matching schema (6) is valid", "data": [3, 4, 6], "valid": true }, { "description": "array without item matching schema is invalid", "data": [1, 2, 3, 4], "valid": false }, { "description": "empty array is invalid", "data": [], "valid": false }, { "description": "not array is valid", "data": {}, "valid": true } ] }, { "description": "contains keyword with const keyword requires a specific item to be present", "schema": { "contains": {"const": 5} }, "tests": [ { "description": "array with item 5 is valid", "data": [3, 4, 5], "valid": true }, { "description": "array without item 5 is invalid", "data": [1, 2, 3, 4], "valid": false } ] } ] ================================================ FILE: spec/extras/exclusiveMaximum.json ================================================ [ { "description": "exclusiveMaximum as number", "schema": { "exclusiveMaximum": 3.0 }, "tests": [ { "description": "below the exclusiveMaximum is valid", "data": 2.2, "valid": true }, { "description": "boundary point is invalid", "data": 3.0, "valid": false }, { "description": "above the exclusiveMaximum is invalid", "data": 3.2, "valid": false } ] }, { "description": "both exclusiveMaximum and maximum are numbers, exclusiveMaximum > maximum", "schema": { "exclusiveMaximum": 3.0, "maximum": 2.0 }, "tests": [ { "description": "below the maximum is valid", "data": 1.2, "valid": true }, { "description": "boundary point is valid", "data": 2.0, "valid": true }, { "description": "above maximum is invalid", "data": 2.2, "valid": false } ] }, { "description": "both exclusiveMaximum and maximum are numbers, exclusiveMaximum = maximum", "schema": { "exclusiveMaximum": 3.0, "maximum": 3.0 }, "tests": [ { "description": "below the maximum is valid", "data": 2.2, "valid": true }, { "description": "boundary point is invalid", "data": 3.0, "valid": false }, { "description": "above maximum is invalid", "data": 3.2, "valid": false } ] }, { "description": "both exclusiveMaximum and maximum are numbers, exclusiveMaximum < maximum", "schema": { "exclusiveMaximum": 2.0, "maximum": 3.0 }, "tests": [ { "description": "below the exclusiveMaximum is valid", "data": 1.2, "valid": true }, { "description": "boundary point is invalid", "data": 2.0, "valid": false }, { "description": "above exclusiveMaximum is invalid", "data": 2.2, "valid": false } ] } ] ================================================ FILE: spec/extras/exclusiveMinimum.json ================================================ [ { "description": "exclusiveMinimum as number", "schema": { "exclusiveMinimum": 1.1 }, "tests": [ { "description": "above the exclusiveMinimum is still valid", "data": 1.2, "valid": true }, { "description": "boundary point is invalid", "data": 1.1, "valid": false }, { "description": "below exclusiveMinimum is invalid", "data": 1.0, "valid": false } ] }, { "description": "both exclusiveMinimum and minimum are numbers, exclusiveMinimum < minimum", "schema": { "exclusiveMinimum": 2.0, "minimum": 3.0 }, "tests": [ { "description": "above the minimum is valid", "data": 3.2, "valid": true }, { "description": "boundary point is valid", "data": 3.0, "valid": true }, { "description": "below minimum is invalid", "data": 2.2, "valid": false } ] }, { "description": "both exclusiveMinimum and minimum are numbers, exclusiveMinimum = minimum", "schema": { "exclusiveMinimum": 3.0, "minimum": 3.0 }, "tests": [ { "description": "above the minimum is valid", "data": 3.2, "valid": true }, { "description": "boundary point is invalid", "data": 3.0, "valid": false }, { "description": "below minimum is invalid", "data": 2.2, "valid": false } ] }, { "description": "both exclusiveMinimum and minimum are numbers, exclusiveMinimum > minimum", "schema": { "exclusiveMinimum": 3.0, "minimum": 2.0 }, "tests": [ { "description": "above the exclusiveMinimum is valid", "data": 3.2, "valid": true }, { "description": "boundary point is invalid", "data": 3.0, "valid": false }, { "description": "below exclusiveMinimum is invalid", "data": 2.2, "valid": false } ] } ] ================================================ FILE: spec/extras.spec.ts ================================================ import getAjvAllInstances from "./ajv_all_instances" import {withStandalone} from "./ajv_standalone" import {_} from "../dist/compile/codegen/code" import jsonSchemaTest = require("json-schema-test") import options from "./ajv_options" import {afterError, afterEach} from "./after_test" import chai from "./chai" const instances = getAjvAllInstances(options, { $data: true, formats: {allowedUnknown: true}, strictTypes: false, strictTuples: false, }) instances.forEach((ajv) => { ajv.opts.code.source = true ajv.opts.code.formats = _`{allowedUnknown: true}` }) jsonSchemaTest(withStandalone(instances), { description: "Extra keywords schemas tests of " + instances.length + " ajv instances with different options", suites: {extras: require("./_json/extras")}, assert: chai.assert, afterError, afterEach, cwd: __dirname, hideFolder: "extras/", timeout: 90000, }) ================================================ FILE: spec/issues/1001_addKeyword_and_schema_without_id.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #1001: addKeyword breaks schema without ID", () => { it("should allow using schemas without ID with addKeyword", () => { const schema = { definitions: { foo: {}, }, } const ajv: any = new _Ajv() ajv.addSchema(schema) ajv.addKeyword("myKeyword") ajv.getSchema("#/definitions/foo").should.be.a("function") }) }) ================================================ FILE: spec/issues/1344_non_root_recursive_ref_standalone.spec.ts ================================================ import _Ajv from "../ajv" import standaloneCode from "../../dist/standalone" import requireFromString = require("require-from-string") import assert = require("assert") const schema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema", $ref: "#/$defs/foo", $defs: { foo: { type: "object", properties: { bar: {$ref: "#/$defs/bar"}, }, }, bar: { oneOf: [{$ref: "#/$defs/foo"}], }, }, } describe("issue #1344: non-root recursive ref with standalone code", () => { it("should compile to standalone code", () => { const ajv = new _Ajv({code: {source: true}}) ajv.addSchema(schema) const validate = ajv.getSchema("schema#/$defs/foo") assert(typeof validate == "function") assert.strictEqual(validate({}), true) const moduleCode = standaloneCode(ajv, validate) const standaloneValidate = requireFromString(moduleCode) assert(typeof standaloneValidate == "function") assert.strictEqual(standaloneValidate({}), true) }) }) ================================================ FILE: spec/issues/1414_base_uri_change.spec.ts ================================================ import _Ajv from "../ajv" import assert = require("assert") const schema1 = { $id: "one", type: "object", properties: { foo: {$ref: "#/definitions/foo"}, }, definitions: { foo: {$ref: "two"}, }, } const schema2 = { $id: "two", type: "object", properties: { bar: {$ref: "#/definitions/bar"}, }, definitions: { bar: {type: "string"}, }, } describe("issue 1414: base URI change", () => { it("should compile schema", () => { const ajv = new _Ajv() ajv.addSchema(schema2) const validate = ajv.compile(schema1) assert.strictEqual(typeof validate, "function") assert.strictEqual(validate({foo: {bar: 1}}), false) assert.strictEqual(validate({foo: {bar: "1"}}), true) }) }) ================================================ FILE: spec/issues/1501_jtd_many_properties.spec.ts ================================================ import type Ajv from "../.." import _Ajv from "../ajv_jtd" import * as assert from "assert" const PROP_COUNT = 10 describe("schema with many properties", () => { let ajv: Ajv const schema = {properties: {}} const data = {} const invalidData = {} before(() => { ajv = new _Ajv() for (let i = 0; i < PROP_COUNT; i++) { const prop = `prop${i}` schema.properties[prop] = {type: "uint16"} data[prop] = i invalidData[prop] = -i } }) it("should correctly compile reference to schema", () => { assert.strictEqual(ajv.validate(schema, data), true) assert.strictEqual(ajv.validate(schema, invalidData), false) }) }) ================================================ FILE: spec/issues/1515_evaluated_properties_nested_anyof.spec.ts ================================================ import _Ajv from "../ajv2019" import * as assert from "assert" describe("tracking evaluated properties with nested anyOf", () => { it("should initialize evaluated properties", () => { const ajv = new _Ajv() const schema = { type: "object", anyOf: [ { required: ["foo"], properties: {foo: {}}, }, { anyOf: [ { properties: {bar: {}}, }, ], }, ], } const validate = ajv.compile(schema) assert.strictEqual(validate({bar: 1}), true) }) }) ================================================ FILE: spec/issues/1539_add_keyword_name_to_validation_error.spec.ts ================================================ import _Ajv from "../ajv2019" import chai from "../chai" const should = chai.should() describe("keyword usage validation error", () => { it("should include the keyword name and schema path in the message", () => { const ajv = new _Ajv({ keywords: [ { keyword: "customKeyword", metaSchema: { type: "string", }, macro() { return {} }, }, ], }) const schema = { type: "object", properties: { foo: { type: "object", customKeyword: { bar: true, }, }, }, } should.throw( () => ajv.compile(schema), 'keyword "customKeyword" value is invalid at path "#/properties/foo": data must be string' ) }) }) ================================================ FILE: spec/issues/1625_evaluated_truthy_pattern_properties.spec.ts ================================================ import _Ajv from "../ajv2020" import * as assert from "assert" describe("tracking evaluated properties with pattern properties of schema = true", () => { it("should initialize evaluated properties", () => { const ajv = new _Ajv() const schema = { type: "object", patternProperties: { "^x-": true, }, unevaluatedProperties: false, } const validate = ajv.compile(schema) assert.strictEqual(validate({bar: 1}), false) assert.strictEqual(validate({"x-bar": false}), true) }) }) ================================================ FILE: spec/issues/1683_re2_engine.spec.ts ================================================ import getAjvAllInstances from "../ajv_all_instances" import {withStandalone} from "../ajv_standalone" import {_} from "../../dist/compile/codegen/code" import jsonSchemaTest = require("json-schema-test") import options from "../ajv_options" import {afterError, afterEach} from "../after_test" import chai from "../chai" import re2 from "../../dist/runtime/re2" import re2tests from "./re2" const instances = getAjvAllInstances(options, { $data: true, formats: {allowedUnknown: true}, strictTypes: false, strictTuples: false, }) instances.forEach((ajv) => { ajv.opts.code.source = true ajv.opts.code.formats = _`{allowedUnknown: true}` ajv.opts.code.regExp = re2 }) jsonSchemaTest(withStandalone(instances), { description: "Test with re2 RegExp engine with " + instances.length + " ajv instances", suites: {"regular expressions": re2tests}, assert: chai.assert, afterError, afterEach, cwd: __dirname, hideFolder: "extras/", timeout: 90000, }) ================================================ FILE: spec/issues/1819_mincontains.spec.ts ================================================ import _Ajv from "../ajv2020" import * as assert from "assert" describe("`minContains: 0` without valid items (issue #1819)", () => { const ajv = new _Ajv() const schema = { type: "array", minContains: 0, maxContains: 1, contains: {type: "number"}, } const validate = ajv.compile(schema) it("no items valid", () => assert.strictEqual(validate(["foo"]), true)) it("1 item valid", () => assert.strictEqual(validate(["foo", 1]), true)) it("2 items invalid", () => assert.strictEqual(validate(["foo", 1, 2]), false)) }) ================================================ FILE: spec/issues/181_allErrors_custom_keyword_skipped.spec.ts ================================================ import _Ajv from "../ajv" import {KeywordDefinition, SchemaValidateFunction} from "../../dist/types" import chai from "../chai" chai.should() describe("issue #181, user-defined keyword is not validated in allErrors mode if there were previous error", () => { it("should validate user-defined keyword that doesn't create errors", () => { testKeywordErrors({ keyword: "alwaysFails", type: "object", errors: true, validate: function v(/* value */) { return false }, }) }) it("should validate keyword that creates errors", () => { const validate: SchemaValidateFunction = (/* value */) => { validate.errors = validate.errors || [] validate.errors.push({ keyword: "alwaysFails", message: "alwaysFails error", params: { keyword: "alwaysFails", }, }) return false } testKeywordErrors({ keyword: "alwaysFails", type: "object", errors: true, validate, }) }) function testKeywordErrors(def: KeywordDefinition): void { const ajv = new _Ajv({allErrors: true}) ajv.addKeyword(def) const schema = { type: "object", required: ["foo"], alwaysFails: true, } const validate: any = ajv.compile(schema) validate({foo: 1}).should.equal(false) validate.errors.should.have.length(1) validate.errors[0].keyword.should.equal("alwaysFails") validate({}).should.equal(false) validate.errors.should.have.length(2) validate.errors[0].keyword.should.equal("required") validate.errors[1].keyword.should.equal("alwaysFails") } }) ================================================ FILE: spec/issues/182_nan_validation.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #182, NaN validation", () => { const ajv = new _Ajv({strictTypes: false}) it("should pass minimum/maximum validation without type", () => { testNaN(ajv, {minimum: 1}, true) testNaN(ajv, {maximum: 1}, true) }) it("should NOT pass minimum/maximum validation without type when strict: false", () => { const _ajv = new _Ajv({strict: false}) testNaN(_ajv, {minimum: 1}, false) testNaN(_ajv, {maximum: 1}, false) }) it("should not pass minimum/maximum validation with type", () => { testNaN(ajv, {type: "number", minimum: 1}, false) testNaN(ajv, {type: "number", maximum: 1}, false) }) it("should pass type: number validation when strict: false", () => { const _ajv = new _Ajv({strict: false}) testNaN(_ajv, {type: "number"}, true) }) it("should not pass type: number validation (changed in v7 - strict by default)", () => { testNaN(ajv, {type: "number"}, false) }) it("should not pass type: integer validation", () => { testNaN(ajv, {type: "integer"}, false) }) function testNaN(_ajv, schema, NaNisValid) { const validate = _ajv.compile(schema) validate(NaN).should.equal(NaNisValid) } }) ================================================ FILE: spec/issues/1935_integer_narrowing_subschema.spec.ts ================================================ import _Ajv from "../ajv" import Ajv from "ajv" import * as assert from "assert" describe("integer valid type in number sub-schema (issue #1935)", () => { let ajv: Ajv before(() => { ajv = new _Ajv({strict: true}) }) it("should allow integer in `if`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", if: { type: "integer", maximum: 5, }, else: { minimum: 10, }, }) )) it("should allow integer in `then`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", if: { multipleOf: 2, }, then: { type: "integer", minimum: 10, }, }) )) it("should allow integer in `else`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", if: { maximum: 5, }, else: { type: "integer", minimum: 10, }, }) )) it("should allow integer in `allOf`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", allOf: [ { type: "integer", minimum: 10, }, { multipleOf: 2, }, ], }) )) it("should allow integer in `oneOf`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", oneOf: [ { type: "integer", minimum: 10, }, { multipleOf: 2, }, ], }) )) it("should allow integer in `anyOf`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", oneOf: [ { type: "integer", minimum: 10, }, { multipleOf: 2, }, ], }) )) it("should allow integer in `not`", () => assert.doesNotThrow(() => ajv.compile({ type: "number", not: { type: "integer", minimum: 10, }, }) )) }) ================================================ FILE: spec/issues/1949_jtd_empty_values.spec.ts ================================================ import _Ajv from "../ajv_jtd" import * as assert from "assert" describe("JTD values with empty schema (issue #1949)", () => { const ajv = new _Ajv() it("should correctly validate empty values form", () => { const schema = {values: {}} const validate = ajv.compile(schema) assert.strictEqual(validate({prop1: 1, prop2: 2}), true) assert.strictEqual(validate({}), true) assert.strictEqual(validate(null), false) assert.strictEqual(validate(1), false) assert.strictEqual(validate("foo"), false) assert.strictEqual(validate(undefined), false) }) it("should correctly validate nullable empty values form", () => { const schema = {values: {}, nullable: true} const validate = ajv.compile(schema) assert.strictEqual(validate({prop1: 1, prop2: 2}), true) assert.strictEqual(validate({}), true) assert.strictEqual(validate(null), true) assert.strictEqual(validate(1), false) assert.strictEqual(validate("foo"), false) assert.strictEqual(validate(undefined), false) }) }) ================================================ FILE: spec/issues/1971_jtd_discriminator.spec.ts ================================================ import _Ajv from "../ajv_jtd" import * as assert from "assert" describe("JTD discriminator with more than 8 (hardcoded in properties.ts) properties (issue #1971)", () => { const ajv = new _Ajv() it("should correctly validate empty values form", () => { const schema = { discriminator: "tag", mapping: { manual: { properties: { first: {type: "uint16"}, second: {type: "uint16"}, third: {type: "uint16"}, fourth: {type: "uint16"}, fifth: {type: "uint16"}, sixth: {type: "uint16"}, additionalOne: {type: "uint16"}, additionalTwo: {type: "uint16"}, }, }, auto: { properties: { first: {type: "uint16"}, second: {type: "uint16"}, third: {type: "uint16"}, fourth: {type: "uint16"}, fifth: {type: "uint16"}, sixth: {type: "uint16"}, additionalThree: {type: "uint16"}, }, }, }, } const data1 = { tag: "manual", first: 1, second: 1, third: 1, fourth: 1, fifth: 1, sixth: 1, additionalOne: 1, additionalTwo: 1, } const data2 = { tag: "auto", first: 1, second: 1, third: 1, fourth: 1, fifth: 1, sixth: 1, additionalThree: 1, } const validate = ajv.compile(schema) assert.strictEqual(validate(data1), true) assert.strictEqual(validate(data2), true) }) }) ================================================ FILE: spec/issues/2001_jtd_only_optional_properties.spec.ts ================================================ import _Ajv from "../ajv_jtd" import * as assert from "assert" describe("JTD schema with optional/additional properties only (issue #2001)", () => { const ajv = new _Ajv() it("should correctly serialize optional properties", () => { const schema = { optionalProperties: { prop0: {type: "uint16"}, prop1: {type: "uint16"}, prop2: {type: "uint16"}, }, additionalProperties: true, } const serialize = ajv.compileSerializer(schema) const test = (data, json) => assert.strictEqual(serialize(data), json) test({prop0: 0, prop1: 1, prop2: 2}, '{"prop0":0,"prop1":1,"prop2":2}') test({prop1: 1, prop2: 2}, '{"prop1":1,"prop2":2}') test({prop0: 0, prop1: 1, prop2: 2, foo: "bar"}, '{"prop0":0,"prop1":1,"prop2":2,"foo":"bar"}') test({prop1: 1, prop2: 2, foo: "bar"}, '{"prop1":1,"prop2":2,"foo":"bar"}') test({foo: "bar"}, '{"foo":"bar"}') }) }) ================================================ FILE: spec/issues/204_options_schemas_data_together.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #204, options schemas and $data used together", () => { it("should use v5 metaschemas by default", () => { const ajv = new _Ajv({ schemas: [{$id: "str", type: "string"}], $data: true, }) const schema = {const: 42} const validate = ajv.compile(schema) validate(42).should.equal(true) validate(43).should.equal(false) ajv.validate("str", "foo").should.equal(true) ajv.validate("str", 42).should.equal(false) }) }) ================================================ FILE: spec/issues/210_mutual_recur_frags.spec.ts ================================================ import type AjvCore from "../../dist/core" import type AjvPack from "../../dist/standalone/instance" import {getStandalone} from "../ajv_standalone" import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #210, mutual recursive $refs that are schema fragments", () => { describe("one ref is fragment", () => { it("should compile and validate schema", spec(new _Ajv())) it("should compile and validate schema: standalone", spec(getStandalone(_Ajv))) function spec(ajv: AjvCore | AjvPack): () => void { return () => { ajv.addSchema({ $id: "foo", definitions: { bar: { type: "object", properties: { baz: { anyOf: [{enum: [42]}, {$ref: "boo"}], }, }, }, }, }) ajv.addSchema({ $id: "boo", type: "object", required: ["quux"], properties: { quux: {$ref: "foo#/definitions/bar"}, }, }) const validate = ajv.compile({$ref: "foo#/definitions/bar"}) validate({baz: {quux: {baz: 42}}}).should.equal(true) validate({baz: {quux: {baz: "foo"}}}).should.equal(false) } } }) describe("both refs are fragments", () => { it("should compile and validate schema", spec(new _Ajv())) it("should compile and validate schema: standalone", spec(getStandalone(_Ajv))) function spec(ajv: AjvCore | AjvPack): () => void { return () => { ajv.addSchema({ $id: "foo", definitions: { bar: { type: "object", properties: { baz: { anyOf: [{enum: [42]}, {$ref: "boo#/definitions/buu"}], }, }, }, }, }) ajv.addSchema({ $id: "boo", definitions: { buu: { type: "object", required: ["quux"], properties: { quux: {$ref: "foo#/definitions/bar"}, }, }, }, }) const validate = ajv.compile({$ref: "foo#/definitions/bar"}) validate({baz: {quux: {baz: 42}}}).should.equal(true) validate({baz: {quux: {baz: "foo"}}}).should.equal(false) } } }) }) ================================================ FILE: spec/issues/240_mutual_recur_frags_common_ref.spec.ts ================================================ import type AjvCore from "../../dist/core" import type AjvPack from "../../dist/standalone/instance" import {getStandalone} from "../ajv_standalone" import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #240, mutually recursive fragment refs reference a common schema", () => { const apiSchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://api.schema#", $defs: { resource: { $id: "#resource", type: "object", properties: { id: {type: "string"}, }, }, resourceIdentifier: { $id: "#resource_identifier", type: "object", properties: { id: {type: "string"}, type: {type: "string"}, }, }, }, } const domainSchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://domain.schema#", type: "object", properties: { data: { oneOf: [ {$ref: "schema://library.schema#resource_identifier"}, {$ref: "schema://catalog_item.schema#resource_identifier"}, ], }, }, } describe("one ref is fragment", () => { it("should compile and validate schema", spec(new _Ajv())) it("should compile and validate schema: standalone", spec(getStandalone(_Ajv))) function spec(ajv: AjvCore | AjvPack): () => void { return () => { const librarySchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://library.schema#", type: "object", properties: { name: {type: "string"}, links: { type: "object", properties: { catalogItems: { type: "array", items: { $ref: "schema://catalog_item_resource_identifier.schema#", }, }, }, }, }, definitions: { resource_identifier: { $id: "#resource_identifier", allOf: [ { type: "object", properties: { type: { type: "string", enum: ["Library"], }, }, }, {$ref: "schema://api.schema#resource_identifier"}, ], }, }, } const catalogItemSchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://catalog_item.schema#", type: "object", properties: { name: {type: "string"}, links: { type: "object", properties: { library: {$ref: "schema://library.schema#resource_identifier"}, }, }, }, definitions: { resource_identifier: { $id: "#resource_identifier", allOf: [ { type: "object", properties: { type: { type: "string", enum: ["CatalogItem"], }, }, }, {$ref: "schema://api.schema#resource_identifier"}, ], }, }, } const catalogItemResourceIdentifierSchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://catalog_item_resource_identifier.schema#", allOf: [ { type: "object", properties: { type: { type: "string", enum: ["CatalogItem"], }, }, }, { $ref: "schema://api.schema#resource_identifier", }, ], } ajv.addSchema(librarySchema) ajv.addSchema(catalogItemSchema) ajv.addSchema(catalogItemResourceIdentifierSchema) ajv.addSchema(apiSchema) const validate = ajv.compile(domainSchema) testSchema(validate) } } }) describe("both refs are fragments", () => { it("should compile and validate schema", spec(new _Ajv())) it("should compile and validate schema: standalone", spec(getStandalone(_Ajv))) function spec(ajv: AjvCore | AjvPack): () => void { return () => { const librarySchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://library.schema#", type: "object", properties: { name: {type: "string"}, links: { type: "object", properties: { catalogItems: { type: "array", items: {$ref: "schema://catalog_item.schema#resource_identifier"}, }, }, }, }, definitions: { resource_identifier: { $id: "#resource_identifier", allOf: [ { type: "object", properties: { type: { type: "string", enum: ["Library"], }, }, }, {$ref: "schema://api.schema#resource_identifier"}, ], }, }, } const catalogItemSchema = { $schema: "http://json-schema.org/draft-07/schema#", $id: "schema://catalog_item.schema#", type: "object", properties: { name: {type: "string"}, links: { type: "object", properties: { library: {$ref: "schema://library.schema#resource_identifier"}, }, }, }, definitions: { resource_identifier: { $id: "#resource_identifier", allOf: [ { type: "object", properties: { type: { type: "string", enum: ["CatalogItem"], }, }, }, {$ref: "schema://api.schema#resource_identifier"}, ], }, }, } ajv.addSchema(librarySchema) ajv.addSchema(catalogItemSchema) ajv.addSchema(apiSchema) const validate = ajv.compile(domainSchema) testSchema(validate) } } }) function testSchema(validate) { validate({data: {type: "Library", id: "123"}}).should.equal(true) validate({data: {type: "Library", id: 123}}).should.equal(false) validate({data: {type: "CatalogItem", id: "123"}}).should.equal(true) validate({data: {type: "CatalogItem", id: 123}}).should.equal(false) validate({data: {type: "Foo", id: "123"}}).should.equal(false) } }) ================================================ FILE: spec/issues/259_validate_meta_against_itself.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #259, support validating [meta-]schemas against themselves", () => { it('should add schema before validation if "id" is the same as "$schema"', () => { const ajv = new _Ajv({strict: false}) const hyperSchema = require("../remotes/hyper-schema.json") ajv.addMetaSchema(hyperSchema) }) }) ================================================ FILE: spec/issues/273_error_schemaPath_refd_schema.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe.skip("issue #273, schemaPath in error in referenced schema", () => { it("should have canonic reference with hash after file name", () => { test(new _Ajv()) test(new _Ajv({inlineRefs: false})) function test(ajv) { const schema = { properties: { a: {$ref: "int"}, }, } const referencedSchema = { id: "int", type: "integer", } ajv.addSchema(referencedSchema) const validate = ajv.compile(schema) validate({a: "foo"}).should.equal(false) validate.errors[0].schemaPath.should.equal("int#/type") } }) }) ================================================ FILE: spec/issues/342_uniqueItems_non-json_objects.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #342, support uniqueItems with some non-JSON objects", () => { let validate before(() => { const ajv = new _Ajv() validate = ajv.compile({type: "array", uniqueItems: true}) }) it("should allow different RegExps", () => { validate([/foo/, /bar/]).should.equal(true) validate([/foo/gi, /foo/gi]).should.equal(false) validate([/foo/, {}]).should.equal(true) }) it("should allow different Dates", () => { validate([new Date("2016-11-11"), new Date("2016-11-12")]).should.equal(true) validate([new Date("2016-11-11"), new Date("2016-11-11")]).should.equal(false) validate([new Date("2016-11-11"), {}]).should.equal(true) }) it("should allow undefined properties", () => { validate([{}, {foo: undefined}]).should.equal(true) validate([{foo: undefined}, {}]).should.equal(true) validate([{foo: undefined}, {bar: undefined}]).should.equal(true) validate([{foo: undefined}, {foo: undefined}]).should.equal(false) }) }) ================================================ FILE: spec/issues/485_type_validation_priority.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #485, order of type validation", () => { it("should validate types before keywords", () => { const ajv = new _Ajv({allErrors: true, strictTypes: false}) const validate: any = ajv.compile({ type: ["integer", "string"], required: ["foo"], minimum: 2, }) validate(2).should.equal(true) validate("foo").should.equal(true) validate(1.5).should.equal(false) checkErrors(["type", "minimum"]) validate({}).should.equal(false) checkErrors(["type", "required"]) function checkErrors(expectedErrs) { validate.errors.should.have.length(expectedErrs.length) expectedErrs.forEach((keyword, i) => validate.errors[i].keyword.should.equal(keyword)) } }) }) ================================================ FILE: spec/issues/50_refs_with_definitions.spec.ts ================================================ import type AjvCore from "../../dist/core" import type AjvPack from "../../dist/standalone/instance" import {getStandalone} from "../ajv_standalone" import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe('issue #50: references with "definitions"', () => { const schema1 = { $id: "http://example.com/test/person.json#", definitions: { name: {type: "string"}, }, type: "object", properties: { name: {$ref: "#/definitions/name"}, }, } const schema2 = { $id: "http://example.com/test/employee.json#", type: "object", properties: { person: {$ref: "/test/person.json#"}, role: {type: "string"}, }, } it("should be supported by addSchema", () => { const ajv = new _Ajv() ajv.addSchema(schema1) ajv.addSchema(schema2) spec(ajv) }) it("should be supported by compile", () => { const ajv = new _Ajv() ajv.compile(schema1) ajv.compile(schema2) spec(ajv) }) it("should be supported by addSchema: standalone", () => { const ajv = getStandalone(_Ajv) ajv.addSchema(schema1) ajv.addSchema(schema2) spec(ajv) }) it("should be supported by compile: standalone", () => { const ajv = getStandalone(_Ajv) ajv.compile(schema1) ajv.compile(schema2) spec(ajv) }) function spec(ajv: AjvCore | AjvPack): void { const result = ajv.validate("http://example.com/test/employee.json#", { person: { name: "Alice", }, role: "Programmer", }) result.should.equal(true) should.equal(ajv.errors, null) } }) ================================================ FILE: spec/issues/521_wrong_warning_id_property.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe('issue #521, incorrect warning with "id" property', () => { it("should not log warning", () => { const ajv = new _Ajv() const consoleWarn = console.warn console.warn = () => { throw new Error("should not log warning") } try { ajv.compile({ $id: "http://example.com/schema.json", type: "object", properties: { id: {type: "string"}, }, required: ["id"], }) } finally { console.warn = consoleWarn } }) }) ================================================ FILE: spec/issues/743_removeAdditional_to_remove_proto.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #743, property __proto__ should be removed with removeAdditional option", () => { it("should remove additional properties", () => { const ajv = new _Ajv({removeAdditional: true}) const schema = { type: "object", properties: { obj: { type: "object", additionalProperties: false, properties: { a: {type: "string"}, b: {type: "string"}, c: {type: "string"}, d: {type: "string"}, e: {type: "string"}, f: {type: "string"}, g: {type: "string"}, h: {type: "string"}, i: {type: "string"}, }, }, }, } const obj = Object.create(null) obj.__proto__ = null // should be removed obj.additional = "will be removed" obj.a = "valid" obj.b = "valid" const data = {obj: obj} ajv.validate(schema, data).should.equal(true) Object.keys(data.obj).should.eql(["a", "b"]) }) }) ================================================ FILE: spec/issues/768_passContext_recursive_ref.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #768, fix passContext in recursive $ref", () => { let ajv, contexts: any[] beforeEach(() => { contexts = [] }) describe("passContext = true", () => { it("should pass this value as context to user-defined keyword validation function", () => { const validate = getValidate(true) const self = {} validate.call(self, {bar: "a", baz: {bar: "b"}}) contexts.should.have.length(2) contexts.forEach((ctx) => ctx.should.equal(self)) }) }) describe("passContext = false", () => { it("should pass ajv instance as context to user-defined keyword validation function", () => { const validate = getValidate(false) validate({bar: "a", baz: {bar: "b"}}) contexts.should.have.length(2) contexts.forEach((ctx) => ctx.should.equal(ajv)) }) }) describe("ref is fragment and passContext = true", () => { it("should pass this value as context to user-defined keyword validation function", () => { const validate = getValidateFragments(true) const self = {} validate.call(self, {baz: {corge: "a", quux: {baz: {corge: "b"}}}}) contexts.should.have.length(2) contexts.forEach((ctx) => ctx.should.equal(self)) }) }) describe("ref is fragment and passContext = false", () => { it("should pass ajv instance as context to user-defined keyword validation function", () => { const validate = getValidateFragments(false) validate({baz: {corge: "a", quux: {baz: {corge: "b"}}}}) contexts.should.have.length(2) contexts.forEach((ctx) => ctx.should.equal(ajv)) }) }) function getValidate(passContext) { ajv = new _Ajv({passContext}) ajv.addKeyword({keyword: "testValidate", validate: storeContext}) const schema = { $id: "foo", type: "object", required: ["bar"], properties: { bar: {testValidate: true}, baz: { $ref: "foo", }, }, } return ajv.compile(schema) } function getValidateFragments(passContext) { ajv = new _Ajv({passContext}) ajv.addKeyword({keyword: "testValidate", validate: storeContext}) ajv.addSchema({ $id: "foo", definitions: { bar: { type: "object", properties: { baz: { $ref: "boo", }, }, }, }, }) ajv.addSchema({ $id: "boo", type: "object", required: ["corge"], properties: { quux: {$ref: "foo#/definitions/bar"}, corge: {testValidate: true}, }, }) return ajv.compile({$ref: "foo#/definitions/bar"}) } function storeContext(this: any) { contexts.push(this) return true } }) ================================================ FILE: spec/issues/815_id_updates_ref_base.spec.ts ================================================ import type {ValidateFunction} from "../.." import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #815, id and $id fields should reset base", () => { let validate: ValidateFunction const schema = { type: "object", properties: { newRoot: { $id: "http://example.com/newRoot", type: "object", properties: { recurse: { $ref: "#", }, name: { type: "string", }, }, required: ["name"], additionalProperties: false, }, }, required: ["newRoot"], additionalProperties: false, } before(() => { validate = new _Ajv().compile(schema) }) it("should set # to reference the closest ancestor with $id", () => { validate({ newRoot: { name: "test", }, }).should.equal(true) validate({ newRoot: { name: "test", recurse: { name: "test2", }, }, }).should.equal(true) }) it("should NOT set # to reference the absolute document root", () => { validate({ newRoot: { name: "test", recurse: { newRoot: { name: "test2", }, }, }, }).should.equal(false) }) }) ================================================ FILE: spec/issues/8_shared_refs.spec.ts ================================================ import type AjvCore from "../../dist/core" import type AjvPack from "../../dist/standalone/instance" import _Ajv from "../ajv" import {getStandalone} from "../ajv_standalone" import chai from "../chai" chai.should() describe("issue #8: schema with shared references", () => { const propertySchema = { type: "string", maxLength: 4, } const schema = { $id: "obj.json#", type: "object", properties: { foo: propertySchema, bar: propertySchema, }, } it("should be supported by addSchema", () => { spec(new _Ajv().addSchema(schema)) }) it("should be supported by compile", () => { const ajv = new _Ajv() ajv.compile(schema) spec(ajv) }) it("should be supported by addSchema: standalone", () => { spec(getStandalone(_Ajv).addSchema(schema)) }) it("should be supported by compile: standalone", () => { const ajv = getStandalone(_Ajv) ajv.compile(schema) spec(ajv) }) function spec(ajv: AjvCore | AjvPack): void { let result = ajv.validate("obj.json#", {foo: "abc", bar: "def"}) result.should.equal(true) result = ajv.validate("obj.json#", {foo: "abcde", bar: "fghg"}) result.should.equal(false) ajv.errors?.should.have.length(1) } }) ================================================ FILE: spec/issues/955_removeAdditional_custom_keywords.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("issue #955: option removeAdditional breaks user-defined keywords", () => { it("should support user-defined keywords with option removeAdditional", () => { const ajv = new _Ajv({removeAdditional: "all"}) ajv.addKeyword({ keyword: "minTrimmedLength", type: "string", compile: function (schema: number) { return function (str: string): boolean { return str.trim().length >= schema } }, metaSchema: {type: "integer"}, }) const schema = { type: "object", properties: { foo: { type: "string", minTrimmedLength: 3, }, }, required: ["foo"], } const validate = ajv.compile(schema) let data = { foo: " bar ", baz: "", } validate(data).should.equal(true) data.should.not.have.property("baz") data = { foo: " ba ", baz: "", } validate(data).should.equal(false) data.should.not.have.property("baz") }) }) ================================================ FILE: spec/issues/cve_2025_69873_redos_attack.spec.ts ================================================ import _Ajv from "../ajv" import re2 from "../../dist/runtime/re2" import chai from "../chai" chai.should() describe("CVE-2025-69873: ReDoS Attack Scenario", () => { it("should prevent ReDoS with RE2 engine for $data pattern injection", () => { const ajv = new _Ajv({$data: true, code: {regExp: re2}}) // Schema that accepts pattern from data const schema = { type: "object", properties: { pattern: {type: "string"}, value: {type: "string", pattern: {$data: "1/pattern"}}, }, } const validate = ajv.compile(schema) // CVE-2025-69873 Attack Payload: // Pattern: ^(a|a)*$ - catastrophic backtracking regex // Value: 30 a's + X - forces full exploration of exponential paths const maliciousPayload = { pattern: "^(a|a)*$", value: "a".repeat(30) + "X", } const start = Date.now() const result = validate(maliciousPayload) const elapsed = Date.now() - start // Should fail validation (pattern doesn't match) result.should.equal(false) // Should complete quickly with RE2 (< 500ms) // Without RE2, this would hang for 44+ seconds elapsed.should.be.below(500) }) it("should handle pattern injection gracefully with default engine", () => { const ajv = new _Ajv({$data: true}) const schema = { type: "object", properties: { pattern: {type: "string"}, value: {type: "string", pattern: {$data: "1/pattern"}}, }, } const validate = ajv.compile(schema) // Attack payload const maliciousPayload = { pattern: "^(a|a)*$", value: "a".repeat(20) + "X", // Reduced size to avoid hanging } // Should complete without crashing (might be slow but won't hang forever) // With try/catch, invalid pattern results in validation failure const result = validate(maliciousPayload) result.should.be.a("boolean") }) it("should handle multiple ReDoS patterns gracefully", () => { const ajv = new _Ajv({$data: true, code: {regExp: re2}}) const schema = { type: "object", properties: { pattern: {type: "string"}, value: {type: "string", pattern: {$data: "1/pattern"}}, }, } const validate = ajv.compile(schema) // Various ReDoS-vulnerable patterns const redosPatterns = ["^(a+)+$", "^(a|a)*$", "^(a|ab)*$", "(x+x+)+y", "(a*)*b"] for (const pattern of redosPatterns) { const start = Date.now() const result = validate({ pattern, value: "a".repeat(25) + "X", }) const elapsed = Date.now() - start // All should complete quickly with RE2 elapsed.should.be.below(500, `Pattern ${pattern} took too long: ${elapsed}ms`) result.should.equal(false) } }) it("should still validate valid patterns correctly", () => { const ajv = new _Ajv({$data: true, code: {regExp: re2}}) const schema = { type: "object", properties: { pattern: {type: "string"}, value: {type: "string", pattern: {$data: "1/pattern"}}, }, } const validate = ajv.compile(schema) // Valid pattern matching tests validate({pattern: "^[a-z]+$", value: "abc"}).should.equal(true) validate({pattern: "^[a-z]+$", value: "ABC"}).should.equal(false) validate({pattern: "^\\d{3}-\\d{4}$", value: "123-4567"}).should.equal(true) validate({pattern: "^\\d{3}-\\d{4}$", value: "12-345"}).should.equal(false) }) it("should fail gracefully on invalid regex syntax in pattern", () => { const ajv = new _Ajv({$data: true, code: {regExp: re2}}) const schema = { type: "object", properties: { pattern: {type: "string"}, value: {type: "string", pattern: {$data: "1/pattern"}}, }, } const validate = ajv.compile(schema) // Invalid regex patterns that RE2 rejects const invalidPatterns = [ "[invalid", // Unclosed bracket "(?P...)", // Perl-style named groups not supported ] for (const pattern of invalidPatterns) { // RE2 rejects these patterns, resulting in validation failure const result = validate({ pattern, value: "test", }) // Invalid patterns should fail validation if (!result) { result.should.equal(false) } } }) it("should process attack payload with safe timing benchmark", () => { const ajv = new _Ajv({$data: true, code: {regExp: re2}}) const schema = { type: "object", properties: { pattern: {type: "string"}, value: {type: "string", pattern: {$data: "1/pattern"}}, }, } const validate = ajv.compile(schema) // Process the exact CVE attack payload const payload = { pattern: "^(a|a)*$", value: "a".repeat(30) + "X", } // With RE2: should complete in < 100ms // Without RE2: would hang for 44+ seconds const start = Date.now() const result = validate(payload) const elapsed = Date.now() - start result.should.equal(false) elapsed.should.be.below(500) }) }) ================================================ FILE: spec/issues/re2.ts ================================================ export default [ {name: "$data/format", test: require("../extras/$data/format.json")}, {name: "$data/pattern", test: require("../extras/$data/pattern.json")}, ] ================================================ FILE: spec/javacript.spec.js ================================================ const Ajv = require("./ajv") const Ajv2019 = require("./ajv2019") const assert = require("assert") describe("using Ajv with javascript", () => { describe("draft-07", () => it("should validate", () => test(Ajv))) describe("draft-2019-09", () => it("should validate", () => test(Ajv2019))) function test(_Ajv) { const ajv = new _Ajv() const validate = ajv.compile({type: "number"}) assert.strictEqual(validate(1), true) assert.strictEqual(validate("1"), false) } }) ================================================ FILE: spec/json-schema.spec.ts ================================================ import type Ajv from "../dist/core" import _Ajv from "./ajv" import _Ajv2019 from "./ajv2019" import _Ajv2020 from "./ajv2020" import getAjvInstances from "./ajv_instances" import {withStandalone} from "./ajv_standalone" import jsonSchemaTest = require("json-schema-test") import options from "./ajv_options" import {afterError, afterEach} from "./after_test" import ajvFormats from "ajv-formats" import draft6MetaSchema = require("../dist/refs/json-schema-draft-06.json") import {toHash} from "../dist/compile/util" import chai from "./chai" const remoteRefs = { "http://localhost:1234/integer.json": require("./JSON-Schema-Test-Suite/remotes/integer.json"), "http://localhost:1234/subSchemas.json": require("./JSON-Schema-Test-Suite/remotes/subSchemas.json"), "http://localhost:1234/subSchemas-defs.json": require("./JSON-Schema-Test-Suite/remotes/subSchemas-defs.json"), "http://localhost:1234/baseUriChange/folderInteger.json": require("./JSON-Schema-Test-Suite/remotes/baseUriChange/folderInteger.json"), "http://localhost:1234/baseUriChangeFolder/folderInteger.json": require("./JSON-Schema-Test-Suite/remotes/baseUriChangeFolder/folderInteger.json"), "http://localhost:1234/baseUriChangeFolderInSubschema/folderInteger.json": require("./JSON-Schema-Test-Suite/remotes/baseUriChangeFolderInSubschema/folderInteger.json"), "http://localhost:1234/name.json": require("./JSON-Schema-Test-Suite/remotes/name.json"), "http://localhost:1234/name-defs.json": require("./JSON-Schema-Test-Suite/remotes/name-defs.json"), } const SKIP_FORMATS = ["idn-email", "idn-hostname", "iri", "iri-reference"] const SKIP_FORMAT_TESTS = SKIP_FORMATS.map((f) => `optional/format/${f}`) const SKIP_DRAFT7 = [ "optional/content", "optional/float-overflow", "unknownKeyword", ...SKIP_FORMAT_TESTS, ] runTest({ instances: getAjvInstances(_Ajv, options, { meta: false, strict: false, ignoreKeywordsWithRef: true, }), draft: 6, tests: skipTestCases(require("./_json/draft6"), { ref: { "$ref prevents a sibling $id from changing the base uri": [ "$ref resolves to /definitions/base_foo, data does not validate", "$ref resolves to /definitions/base_foo, data validates", ], }, }), remotes: { "http://localhost:1234/ref-and-definitions.json": require("./JSON-Schema-Test-Suite/remotes/ref-and-definitions.json"), }, skip: ["optional/float-overflow", "unknownKeyword"], }) runTest({ instances: getAjvInstances(_Ajv, options, { strict: false, ignoreKeywordsWithRef: true, formats: toHash(SKIP_FORMATS), }), draft: 7, tests: skipTestCases(require("./_json/draft7"), { ref: { "$ref prevents a sibling $id from changing the base uri": [ "$ref resolves to /definitions/base_foo, data does not validate", "$ref resolves to /definitions/base_foo, data validates", ], }, }), remotes: { "http://localhost:1234/ref-and-definitions.json": require("./JSON-Schema-Test-Suite/remotes/ref-and-definitions.json"), }, skip: SKIP_DRAFT7, }) runTest({ instances: getAjvInstances(_Ajv2019, options, { strict: false, formats: toHash(SKIP_FORMATS), }), draft: 2019, tests: skipTestCases(require("./_json/draft2019"), { recursiveRef: { "$recursiveRef with no $recursiveAnchor in the initial target schema resource": [ "leaf node matches: recursion uses the inner schema", "leaf node does not match: recursion uses the inner schema", ], }, ref: { "refs with relative uris and defs": [ "invalid on inner field", "invalid on outer field", "valid on both fields", ], "relative refs with absolute uris and defs": [ "invalid on inner field", "invalid on outer field", "valid on both fields", ], }, unevaluatedProperties: { "unevaluatedProperties with if/then/else, then not defined": [ "when if is false and has unevaluated properties", ], }, }), remotes: { "http://localhost:1234/ref-and-defs.json": require("./JSON-Schema-Test-Suite/remotes/ref-and-defs.json"), "http://localhost:1234/draft2019-09/metaschema-no-validation.json": require("./JSON-Schema-Test-Suite/remotes/draft2019-09/metaschema-no-validation.json"), }, skip: SKIP_DRAFT7, }) runTest({ instances: getAjvInstances(_Ajv2020, options, { strict: false, formats: toHash(SKIP_FORMATS), }), draft: 2020, tests: skipTestCases(require("./_json/draft2020"), { dynamicRef: { "A $dynamicRef to a $dynamicAnchor in the same schema resource should behave like a normal $ref to an $anchor": ["An array of strings is valid"], "A $dynamicRef to an $anchor in the same schema resource should behave like a normal $ref to an $anchor": ["An array of strings is valid"], "A $dynamicRef should resolve to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated": ["An array of strings is valid"], "A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor should not affect dynamic scope resolution": ["An array of strings is valid"], "An $anchor with the same name as a $dynamicAnchor should not be used for dynamic scope resolution": ["Any array is valid"], "A $dynamicRef without a matching $dynamicAnchor in the same schema resource should behave like a normal $ref to $anchor": ["Any array is valid"], "A $dynamicRef with a non-matching $dynamicAnchor in the same schema resource should behave like a normal $ref to $anchor": ["Any array is valid"], "A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor should resolve to the first $dynamicAnchor in the dynamic scope": [ "The recursive part is valid against the root", "The recursive part is not valid against the root", ], "A $dynamicRef that initially resolves to a schema without a matching $dynamicAnchor should behave like a normal $ref to $anchor": ["The recursive part doesn't need to validate against the root"], "after leaving a dynamic scope, it should not be used by a $dynamicRef": [ "string matches /$defs/thingy, but the $dynamicRef does not stop here", "first_scope is not in dynamic scope for the $dynamicRef", "/then/$defs/thingy is the final stop for the $dynamicRef", ], "strict-tree schema, guards against misspelled properties": [ "instance with misspelled field", "instance with correct field", ], "tests for implementation dynamic anchor and reference link": [ "incorrect parent schema", "incorrect extended schema", "correct extended schema", ], // duplicate "Tests for implementation dynamic anchor and reference link. Reference should be independent of any possible ordering.": ["incorrect parent schema", "incorrect extended schema", "correct extended schema"], }, ref: { "refs with relative uris and defs": [ "invalid on inner field", "invalid on outer field", "valid on both fields", ], "relative refs with absolute uris and defs": [ "invalid on inner field", "invalid on outer field", "valid on both fields", ], }, unevaluatedItems: { "unevaluatedItems depends on adjacent contains": [ "contains passes, second item is not evaluated", ], "unevaluatedItems depends on multiple nested contains": [ "7 not evaluated, fails unevaluatedItems", ], "unevaluatedItems and contains interact to control item dependency relationship": [ "only b's are invalid", "only c's are invalid", "only b's and c's are invalid", "only a's and c's are invalid", ], }, unevaluatedProperties: { "unevaluatedProperties with if/then/else, then not defined": [ "when if is false and has unevaluated properties", ], }, }), remotes: { "http://localhost:1234/ref-and-defs.json": require("./JSON-Schema-Test-Suite/remotes/ref-and-defs.json"), "http://localhost:1234/draft2020-12/format-assertion-false.json": require("./JSON-Schema-Test-Suite/remotes/draft2020-12/format-assertion-false.json"), "http://localhost:1234/draft2020-12/format-assertion-true.json": require("./JSON-Schema-Test-Suite/remotes/draft2020-12/format-assertion-true.json"), "http://localhost:1234/draft2020-12/metaschema-no-validation.json": require("./JSON-Schema-Test-Suite/remotes/draft2020-12/metaschema-no-validation.json"), }, skip: [...SKIP_DRAFT7, "optional/format-assertion"], }) interface TestSuite { name: string test: any[] } interface SchemaTest { instances: Ajv[] draft: number tests: TestSuite[] skip?: string[] remotes?: Record } function runTest({instances, draft, tests, skip = [], remotes = {}}: SchemaTest) { for (const ajv of instances) { ajv.opts.code.source = true if (draft === 6) { ajv.addMetaSchema(draft6MetaSchema) ajv.opts.defaultMeta = "http://json-schema.org/draft-06/schema#" } for (const id in remoteRefs) ajv.addSchema(remoteRefs[id], id) for (const id in remotes) ajv.addSchema(remotes[id], id) ajvFormats(ajv) } jsonSchemaTest(withStandalone(instances), { description: `JSON-Schema Test Suite draft-${draft}: ${instances.length} ajv instances with different options`, suites: {tests}, only: [], skip, assert: chai.assert, afterError, afterEach, cwd: __dirname, hideFolder: `draft${draft}/`, timeout: 30000, }) } interface SkippedTestCases { [suite: string]: { [test: string]: string[] | true } } function skipTestCases(suites: TestSuite[], skipCases: SkippedTestCases): TestSuite[] { for (const suiteName in skipCases) { const suite = suites.find(({name}) => name === suiteName) if (!suite) throw new Error(`test suite ${suiteName} not found`) for (const testName in skipCases[suiteName]) { const test = suite.test.find(({description}) => description === testName) if (!test) { throw new Error(`test ${testName} not found in suite ${suiteName}`) } const skippedCases = skipCases[suiteName][testName] suite.test.forEach((t) => { if (t.description === testName) { if (skippedCases === true) { t.skip = true } else { t.tests.forEach((testCase: any) => { if (skippedCases.includes(testCase.description)) { testCase.skip = true } }) } } }) } } return suites } ================================================ FILE: spec/json_parse_tests.json ================================================ [ { "suite": "number", "tests": [ { "name": "number ", "valid": true, "json": "[123e65]", "data": [1.23e67] }, { "name": "number ++", "valid": false, "json": "[++1234]" }, { "name": "number +1", "valid": false, "json": "[+1]" }, { "name": "number +Inf", "valid": false, "json": "[+Inf]" }, { "name": "number -01", "valid": false, "json": "[-01]" }, { "name": "number -1.0.", "valid": false, "json": "[-1.0.]" }, { "name": "number -2.", "valid": false, "json": "[-2.]" }, { "name": "number -NaN", "valid": false, "json": "[-NaN]" }, { "name": "number .-1", "valid": false, "json": "[.-1]" }, { "name": "number .2e-3", "valid": false, "json": "[.2e-3]" }, { "name": "number 0 capital E", "valid": false, "json": "[0E]" }, { "name": "number 0 capital E+", "valid": false, "json": "[0E+]" }, { "name": "number 0.1.2", "valid": false, "json": "[0.1.2]" }, { "name": "number 0.3e", "valid": false, "json": "[0.3e]" }, { "name": "number 0.3e+", "valid": false, "json": "[0.3e+]" }, { "name": "number 0.e1", "valid": false, "json": "[0.e1]" }, { "name": "number 0e", "valid": false, "json": "[0e]" }, { "name": "number 0e+", "valid": false, "json": "[0e+]" }, { "name": "number 0e+1", "valid": true, "json": "[0e+1]", "data": [0] }, { "name": "number 0e1", "valid": true, "json": "[0e1]", "data": [0] }, { "name": "number 1 000", "valid": false, "json": "[1 000.0]" }, { "name": "number 1.0e", "valid": false, "json": "[1.0e]" }, { "name": "number 1.0e+", "valid": false, "json": "[1.0e+]" }, { "name": "number 1.0e-", "valid": false, "json": "[1.0e-]" }, { "name": "number 1eE2", "valid": false, "json": "[1eE2]" }, { "name": "number 2.e+3", "valid": false, "json": "[2.e+3]" }, { "name": "number 2.e-3", "valid": false, "json": "[2.e-3]" }, { "name": "number 2.e3", "valid": false, "json": "[2.e3]" }, { "name": "number 9.e+", "valid": false, "json": "[9.e+]" }, { "name": "number Inf", "valid": false, "json": "[Inf]" }, { "name": "number NaN", "valid": false, "json": "[NaN]" }, { "name": "number U+FF11 fullwidth digit one", "valid": false, "json": "[1]" }, { "name": "number after space", "valid": true, "json": "[ 4]", "data": [4] }, { "name": "number double close to zero", "valid": true, "json": "[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]\n", "data": [-1e-78] }, { "name": "number double huge neg exp", "valid": null, "json": "[123.456e-789]" }, { "name": "number expression", "valid": false, "json": "[1+2]" }, { "name": "number hex 1 digit", "valid": false, "json": "[0x1]" }, { "name": "number hex 2 digits", "valid": false, "json": "[0x42]" }, { "name": "number huge exp", "valid": null, "json": "[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]" }, { "name": "number infinity", "valid": false, "json": "[Infinity]" }, { "name": "number int with exp", "valid": true, "json": "[20e1]", "data": [200] }, { "name": "number invalid+-", "valid": false, "json": "[0e+-1]" }, { "name": "number invalid-negative-real", "valid": false, "json": "[-123.123foo]" }, { "name": "number invalid-utf-8-in-bigger-int", "valid": false, "json": "[123�]" }, { "name": "number invalid-utf-8-in-exponent", "valid": false, "json": "[1e1�]" }, { "name": "number invalid-utf-8-in-int", "valid": false, "json": "[0�]\n" }, { "name": "number minus infinity", "valid": false, "json": "[-Infinity]" }, { "name": "number minus sign with trailing garbage", "valid": false, "json": "[-foo]" }, { "name": "number minus space 1", "valid": false, "json": "[- 1]" }, { "name": "number minus zero", "valid": true, "json": "[-0]", "data": [-0] }, { "name": "number neg int huge exp", "valid": null, "json": "[-1e+9999]" }, { "name": "number neg int starting with zero", "valid": false, "json": "[-012]" }, { "name": "number neg real without int part", "valid": false, "json": "[-.123]" }, { "name": "number neg with garbage at end", "valid": false, "json": "[-1x]" }, { "name": "number negative int", "valid": true, "json": "[-123]", "data": [-123] }, { "name": "number negative one", "valid": true, "json": "[-1]", "data": [-1] }, { "name": "number negative zero", "valid": true, "json": "[-0]", "data": [-0] }, { "name": "number pos double huge exp", "valid": null, "json": "[1.5e+9999]" }, { "name": "number real capital e", "valid": true, "json": "[1E22]", "data": [1e22] }, { "name": "number real capital e neg exp", "valid": true, "json": "[1E-2]", "data": [0.01] }, { "name": "number real capital e pos exp", "valid": true, "json": "[1E+2]", "data": [100] }, { "name": "number real exponent", "valid": true, "json": "[123e45]", "data": [1.23e47] }, { "name": "number real fraction exponent", "valid": true, "json": "[123.456e78]", "data": [1.23456e80] }, { "name": "number real garbage after e", "valid": false, "json": "[1ea]" }, { "name": "number real neg exp", "valid": true, "json": "[1e-2]", "data": [0.01] }, { "name": "number real neg overflow", "valid": null, "json": "[-123123e100000]" }, { "name": "number real pos exponent", "valid": true, "json": "[1e+2]", "data": [100] }, { "name": "number real pos overflow", "valid": null, "json": "[123123e100000]" }, { "name": "number real underflow", "valid": null, "json": "[123e-10000000]" }, { "name": "number real with invalid utf8 after e", "valid": false, "json": "[1e�]" }, { "name": "number real without fractional part", "valid": false, "json": "[1.]" }, { "name": "number simple int", "valid": true, "json": "[123]", "data": [123] }, { "name": "number simple real", "valid": true, "json": "[123.456789]", "data": [123.456789] }, { "name": "number starting with dot", "valid": false, "json": "[.123]" }, { "name": "number too big neg int", "valid": null, "json": "[-123123123123123123123123123123]" }, { "name": "number too big pos int", "valid": null, "json": "[100000000000000000000]" }, { "name": "number very big negative int", "valid": null, "json": "[-237462374673276894279832749832423479823246327846]" }, { "name": "number with alpha", "valid": false, "json": "[1.2a-3]" }, { "name": "number with alpha char", "valid": false, "json": "[1.8011670033376514H-308]" }, { "name": "number with leading zero", "valid": false, "json": "[012]" } ] }, { "suite": "object", "tests": [ { "name": "object ", "valid": true, "json": "{\"asd\":\"sdf\", \"dfg\":\"fgh\"}", "data": { "asd": "sdf", "dfg": "fgh" } }, { "name": "object bad value", "valid": false, "json": "[\"x\", truth]" }, { "name": "object basic", "valid": true, "json": "{\"asd\":\"sdf\"}", "data": { "asd": "sdf" } }, { "name": "object bracket key", "valid": false, "json": "{[: \"x\"}\n" }, { "name": "object comma instead of colon", "valid": false, "json": "{\"x\", null}" }, { "name": "object double colon", "valid": false, "json": "{\"x\"::\"b\"}" }, { "name": "object duplicated key", "valid": true, "json": "{\"a\":\"b\",\"a\":\"c\"}", "data": { "a": "c" } }, { "name": "object duplicated key and value", "valid": true, "json": "{\"a\":\"b\",\"a\":\"b\"}", "data": { "a": "b" } }, { "name": "object emoji", "valid": false, "json": "{🇨🇭}" }, { "name": "object empty", "valid": true, "json": "{}", "data": {} }, { "name": "object empty key", "valid": true, "json": "{\"\":0}", "data": { "": 0 } }, { "name": "object escaped null in key", "valid": true, "json": "{\"foo\\u0000bar\": 42}", "data": { "foo\u0000bar": 42 } }, { "name": "object extreme numbers", "valid": true, "json": "{ \"min\": -1.0e+28, \"max\": 1.0e+28 }", "data": { "min": -1e28, "max": 1e28 } }, { "name": "object garbage at end", "valid": false, "json": "{\"a\":\"a\" 123}" }, { "name": "object key lone 2nd surrogate", "valid": null, "json": "{\"\\uDFAA\":0}" }, { "name": "object key with single quotes", "valid": false, "json": "{key: 'value'}" }, { "name": "object lone continuation byte in key and trailing comma", "valid": false, "json": "{\"�\":\"0\",}" }, { "name": "object long strings", "valid": true, "json": "{\"x\":[{\"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}], \"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}", "data": { "x": [ { "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } }, { "name": "object missing colon", "valid": false, "json": "{\"a\" b}" }, { "name": "object missing key", "valid": false, "json": "{:\"b\"}" }, { "name": "object missing semicolon", "valid": false, "json": "{\"a\" \"b\"}" }, { "name": "object missing value", "valid": false, "json": "{\"a\":" }, { "name": "object no-colon", "valid": false, "json": "{\"a\"" }, { "name": "object non string key", "valid": false, "json": "{1:1}" }, { "name": "object non string key but huge number instead", "valid": false, "json": "{9999E9999:1}" }, { "name": "object repeated null null", "valid": false, "json": "{null:null,null:null}" }, { "name": "object several trailing commas", "valid": false, "json": "{\"id\":0,,,,,}" }, { "name": "object simple", "valid": true, "json": "{\"a\":[]}", "data": { "a": [] } }, { "name": "object single quote", "valid": false, "json": "{'a':0}" }, { "name": "object string unicode", "valid": true, "json": "{\"title\":\"\\u041f\\u043e\\u043b\\u0442\\u043e\\u0440\\u0430 \\u0417\\u0435\\u043c\\u043b\\u0435\\u043a\\u043e\\u043f\\u0430\" }", "data": { "title": "Полтора Землекопа" } }, { "name": "object trailing comma", "valid": false, "json": "{\"id\":0,}" }, { "name": "object trailing comment", "valid": false, "json": "{\"a\":\"b\"}/**/" }, { "name": "object trailing comment open", "valid": false, "json": "{\"a\":\"b\"}/**//" }, { "name": "object trailing comment slash open", "valid": false, "json": "{\"a\":\"b\"}//" }, { "name": "object trailing comment slash open incomplete", "valid": false, "json": "{\"a\":\"b\"}/" }, { "name": "object two commas in a row", "valid": false, "json": "{\"a\":\"b\",,\"c\":\"d\"}" }, { "name": "object unquoted key", "valid": false, "json": "{a: \"b\"}" }, { "name": "object unterminated-value", "valid": false, "json": "{\"a\":\"a" }, { "name": "object with newlines", "valid": true, "json": "{\n\"a\": \"b\"\n}", "data": { "a": "b" } }, { "name": "object with single string", "valid": false, "json": "{ \"foo\" : \"bar\", \"a\" }" }, { "name": "object with trailing garbage", "valid": false, "json": "{\"a\":\"b\"}#" } ] }, { "suite": "string", "tests": [ { "name": "string 1 2 3 bytes UTF-8 sequences", "valid": true, "json": "[\"\\u0060\\u012a\\u12AB\"]", "data": ["`Īካ"] }, { "name": "string 1 surrogate then escape", "valid": false, "json": "[\"\\uD800\\\"]" }, { "name": "string 1 surrogate then escape u", "valid": false, "json": "[\"\\uD800\\u\"]" }, { "name": "string 1 surrogate then escape u1", "valid": false, "json": "[\"\\uD800\\u1\"]" }, { "name": "string 1 surrogate then escape u1x", "valid": false, "json": "[\"\\uD800\\u1x\"]" }, { "name": "string 1st surrogate but 2nd missing", "valid": null, "json": "[\"\\uDADA\"]" }, { "name": "string 1st valid surrogate 2nd invalid", "valid": null, "json": "[\"\\uD888\\u1234\"]" }, { "name": "string UTF-16LE with BOM", "valid": null, "json": "��[\u0000\"\u0000�\u0000\"\u0000]\u0000" }, { "name": "string UTF-8 invalid sequence", "valid": null, "json": "[\"日ш�\"]" }, { "name": "string UTF8 surrogate U+D800", "valid": null, "json": "[\"���\"]" }, { "name": "string accentuated char no quotes", "valid": false, "json": "[é]" }, { "name": "string accepted surrogate pair", "valid": true, "json": "[\"\\uD801\\udc37\"]", "data": ["𐐷"] }, { "name": "string accepted surrogate pairs", "valid": true, "json": "[\"\\ud83d\\ude39\\ud83d\\udc8d\"]", "data": ["😹💍"] }, { "name": "string allowed escapes", "valid": true, "json": "[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]", "data": ["\"\\/\b\f\n\r\t"] }, { "name": "string backslash 00", "valid": false, "json": "[\"\\\u0000\"]" }, { "name": "string backslash and u escaped zero", "valid": true, "json": "[\"\\\\u0000\"]", "data": ["\\u0000"] }, { "name": "string backslash doublequotes", "valid": true, "json": "[\"\\\"\"]", "data": ["\""] }, { "name": "string comments", "valid": true, "json": "[\"a/*b*/c/*d//e\"]", "data": ["a/*b*/c/*d//e"] }, { "name": "string double escape a", "valid": true, "json": "[\"\\\\a\"]", "data": ["\\a"] }, { "name": "string double escape n", "valid": true, "json": "[\"\\\\n\"]", "data": ["\\n"] }, { "name": "string escape x", "valid": false, "json": "[\"\\x00\"]" }, { "name": "string escaped backslash bad", "valid": false, "json": "[\"\\\\\\\"]" }, { "name": "string escaped control character", "valid": true, "json": "[\"\\u0012\"]", "data": ["\u0012"] }, { "name": "string escaped ctrl char tab", "valid": false, "json": "[\"\\\t\"]" }, { "name": "string escaped emoji", "valid": false, "json": "[\"\\🌀\"]" }, { "name": "string escaped noncharacter", "valid": true, "json": "[\"\\uFFFF\"]", "data": ["￿"] }, { "name": "string in array", "valid": true, "json": "[\"asd\"]", "data": ["asd"] }, { "name": "string in array with leading space", "valid": true, "json": "[ \"asd\"]", "data": ["asd"] }, { "name": "string incomplete escape", "valid": false, "json": "[\"\\\"]" }, { "name": "string incomplete escaped character", "valid": false, "json": "[\"\\u00A\"]" }, { "name": "string incomplete surrogate", "valid": false, "json": "[\"\\uD834\\uDd\"]" }, { "name": "string incomplete surrogate and escape valid", "valid": null, "json": "[\"\\uD800\\n\"]" }, { "name": "string incomplete surrogate escape invalid", "valid": false, "json": "[\"\\uD800\\uD800\\x\"]" }, { "name": "string incomplete surrogate pair", "valid": null, "json": "[\"\\uDd1ea\"]" }, { "name": "string incomplete surrogates escape valid", "valid": null, "json": "[\"\\uD800\\uD800\\n\"]" }, { "name": "string invalid backslash esc", "valid": false, "json": "[\"\\a\"]" }, { "name": "string invalid lonely surrogate", "valid": null, "json": "[\"\\ud800\"]" }, { "name": "string invalid surrogate", "valid": null, "json": "[\"\\ud800abc\"]" }, { "name": "string invalid unicode escape", "valid": false, "json": "[\"\\uqqqq\"]" }, { "name": "string invalid utf-8", "valid": null, "json": "[\"�\"]" }, { "name": "string invalid utf8 after escape", "valid": false, "json": "[\"\\�\"]" }, { "name": "string invalid-utf-8-in-escape", "valid": false, "json": "[\"\\u�\"]" }, { "name": "string inverted surrogates U+1D11E", "valid": null, "json": "[\"\\uDd1e\\uD834\"]" }, { "name": "string iso latin 1", "valid": null, "json": "[\"�\"]" }, { "name": "string last surrogates 1 and 2", "valid": true, "json": "[\"\\uDBFF\\uDFFF\"]", "data": ["􏿿"] }, { "name": "string leading uescaped thinspace", "valid": false, "json": "[\\u0020\"asd\"]" }, { "name": "string lone second surrogate", "valid": null, "json": "[\"\\uDFAA\"]" }, { "name": "string lone utf8 continuation byte", "valid": null, "json": "[\"�\"]" }, { "name": "string nbsp uescaped", "valid": true, "json": "[\"new\\u00A0line\"]", "data": ["new line"] }, { "name": "string no quotes with bad escape", "valid": false, "json": "[\\n]" }, { "name": "string nonCharacterInUTF-8 U+10FFFF", "valid": true, "json": "[\"􏿿\"]", "data": ["􏿿"] }, { "name": "string nonCharacterInUTF-8 U+FFFF", "valid": true, "json": "[\"￿\"]", "data": ["￿"] }, { "name": "string not in unicode range", "valid": null, "json": "[\"����\"]" }, { "name": "string null escape", "valid": true, "json": "[\"\\u0000\"]", "data": ["\u0000"] }, { "name": "string one-byte-utf-8", "valid": true, "json": "[\"\\u002c\"]", "data": [","] }, { "name": "string overlong sequence 2 bytes", "valid": null, "json": "[\"��\"]" }, { "name": "string overlong sequence 6 bytes", "valid": null, "json": "[\"������\"]" }, { "name": "string overlong sequence 6 bytes null", "valid": null, "json": "[\"������\"]" }, { "name": "string pi", "valid": true, "json": "[\"π\"]", "data": ["π"] }, { "name": "string reservedCharacterInUTF-8 U+1BFFF", "valid": true, "json": "[\"𛿿\"]", "data": ["𛿿"] }, { "name": "string simple ascii", "valid": true, "json": "[\"asd \"]", "data": ["asd "] }, { "name": "string single doublequote", "valid": false, "json": "\"" }, { "name": "string single quote", "valid": false, "json": "['single quote']" }, { "name": "string single string no double quotes", "valid": false, "json": "abc" }, { "name": "string space", "valid": true, "json": "[\" \"]", "data": [" "] }, { "name": "string start escape unclosed", "valid": false, "json": "[\"\\" }, { "name": "string surrogates U+1D11E MUSICAL SYMBOL G CLEF", "valid": true, "json": "[\"\\uD834\\uDd1e\"]", "data": ["𝄞"] }, { "name": "string three-byte-utf-8", "valid": true, "json": "[\"\\u0821\"]", "data": ["ࠡ"] }, { "name": "string truncated-utf-8", "valid": null, "json": "[\"��\"]" }, { "name": "string two-byte-utf-8", "valid": true, "json": "[\"\\u0123\"]", "data": ["ģ"] }, { "name": "string u+2028 line sep", "valid": true, "json": "[\"
\"]", "data": ["
"] }, { "name": "string u+2029 par sep", "valid": true, "json": "[\"
\"]", "data": ["
"] }, { "name": "string uEscape", "valid": true, "json": "[\"\\u0061\\u30af\\u30EA\\u30b9\"]", "data": ["aクリス"] }, { "name": "string uescaped newline", "valid": true, "json": "[\"new\\u000Aline\"]", "data": ["new\nline"] }, { "name": "string unescaped char delete", "valid": true, "json": "[\"\"]", "data": [""] }, { "name": "string unescaped ctrl char", "valid": false, "json": "[\"a\u0000a\"]" }, { "name": "string unescaped newline", "valid": false, "json": "[\"new\nline\"]" }, { "name": "string unescaped tab", "valid": false, "json": "[\"\t\"]" }, { "name": "string unicode", "valid": true, "json": "[\"\\uA66D\"]", "data": ["ꙭ"] }, { "name": "string unicode 2", "valid": true, "json": "[\"⍂㈴⍂\"]", "data": ["⍂㈴⍂"] }, { "name": "string unicode CapitalU", "valid": false, "json": "\"\\UA66D\"" }, { "name": "string unicode U+10FFFE nonchar", "valid": true, "json": "[\"\\uDBFF\\uDFFE\"]", "data": ["􏿾"] }, { "name": "string unicode U+1FFFE nonchar", "valid": true, "json": "[\"\\uD83F\\uDFFE\"]", "data": ["🿾"] }, { "name": "string unicode U+200B ZERO WIDTH SPACE", "valid": true, "json": "[\"\\u200B\"]", "data": ["​"] }, { "name": "string unicode U+2064 invisible plus", "valid": true, "json": "[\"\\u2064\"]", "data": ["⁤"] }, { "name": "string unicode U+FDD0 nonchar", "valid": true, "json": "[\"\\uFDD0\"]", "data": ["﷐"] }, { "name": "string unicode U+FFFE nonchar", "valid": true, "json": "[\"\\uFFFE\"]", "data": ["￾"] }, { "name": "string unicode escaped double quote", "valid": true, "json": "[\"\\u0022\"]", "data": ["\""] }, { "name": "string unicodeEscapedBackslash", "valid": true, "json": "[\"\\u005C\"]", "data": ["\\"] }, { "name": "string utf16BE no BOM", "valid": null, "json": "\u0000[\u0000\"\u0000�\u0000\"\u0000]" }, { "name": "string utf16LE no BOM", "valid": null, "json": "[\u0000\"\u0000�\u0000\"\u0000]\u0000" }, { "name": "string utf8", "valid": true, "json": "[\"€𝄞\"]", "data": ["€𝄞"] }, { "name": "string with del character", "valid": true, "json": "[\"aa\"]", "data": ["aa"] }, { "name": "string with trailing garbage", "valid": false, "json": "\"\"x" } ] }, { "suite": "structure", "tests": [ { "name": "structure 100000 opening arrays", "valid": false, "json": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" }, { "name": "structure 500 nested arrays", "valid": null, "json": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" }, { "name": "structure U+2060 word joined", "valid": false, "json": "[⁠]" }, { "name": "structure UTF-8 BOM empty object", "valid": null, "json": "{}" }, { "name": "structure UTF8 BOM no data", "valid": false, "json": "" }, { "name": "structure angle bracket .", "valid": false, "json": "<.>" }, { "name": "structure angle bracket null", "valid": false, "json": "[]" }, { "name": "structure array trailing garbage", "valid": false, "json": "[1]x" }, { "name": "structure array with extra array close", "valid": false, "json": "[1]]" }, { "name": "structure array with unclosed string", "valid": false, "json": "[\"asd]" }, { "name": "structure ascii-unicode-identifier", "valid": false, "json": "aå" }, { "name": "structure capitalized True", "valid": false, "json": "[True]" }, { "name": "structure close unopened array", "valid": false, "json": "1]" }, { "name": "structure comma instead of closing brace", "valid": false, "json": "{\"x\": true," }, { "name": "structure double array", "valid": false, "json": "[][]" }, { "name": "structure end array", "valid": false, "json": "]" }, { "name": "structure incomplete UTF8 BOM", "valid": false, "json": "�{}" }, { "name": "structure lone-invalid-utf-8", "valid": false, "json": "�" }, { "name": "structure lone-open-bracket", "valid": false, "json": "[" }, { "name": "structure lonely false", "valid": true, "json": "false", "data": false }, { "name": "structure lonely int", "valid": true, "json": "42", "data": 42 }, { "name": "structure lonely negative real", "valid": true, "json": "-0.1", "data": -0.1 }, { "name": "structure lonely null", "valid": true, "json": "null", "data": null }, { "name": "structure lonely string", "valid": true, "json": "\"asd\"", "data": "asd" }, { "name": "structure lonely true", "valid": true, "json": "true", "data": true }, { "name": "structure no data", "valid": false, "json": "" }, { "name": "structure null-byte-outside-string", "valid": false, "json": "[\u0000]" }, { "name": "structure number with trailing garbage", "valid": false, "json": "2@" }, { "name": "structure object followed by closing object", "valid": false, "json": "{}}" }, { "name": "structure object unclosed no value", "valid": false, "json": "{\"\":" }, { "name": "structure object with comment", "valid": false, "json": "{\"a\":/*comment*/\"b\"}" }, { "name": "structure object with trailing garbage", "valid": false, "json": "{\"a\": true} \"x\"" }, { "name": "structure open array apostrophe", "valid": false, "json": "['" }, { "name": "structure open array comma", "valid": false, "json": "[," }, { "name": "structure open array object", "valid": false, "json": "[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":[{\"\":\n" }, { "name": "structure open array open object", "valid": false, "json": "[{" }, { "name": "structure open array open string", "valid": false, "json": "[\"a" }, { "name": "structure open array string", "valid": false, "json": "[\"a\"" }, { "name": "structure open object", "valid": false, "json": "{" }, { "name": "structure open object close array", "valid": false, "json": "{]" }, { "name": "structure open object comma", "valid": false, "json": "{," }, { "name": "structure open object open array", "valid": false, "json": "{[" }, { "name": "structure open object open string", "valid": false, "json": "{\"a" }, { "name": "structure open object string with apostrophes", "valid": false, "json": "{'a'" }, { "name": "structure open open", "valid": false, "json": "[\"\\{[\"\\{[\"\\{[\"\\{" }, { "name": "structure single eacute", "valid": false, "json": "�" }, { "name": "structure single star", "valid": false, "json": "*" }, { "name": "structure string empty", "valid": true, "json": "\"\"", "data": "" }, { "name": "structure trailing #", "valid": false, "json": "{\"a\":\"b\"}#{}" }, { "name": "structure trailing newline", "valid": true, "json": "[\"a\"]\n", "data": ["a"] }, { "name": "structure true in array", "valid": true, "json": "[true]", "data": [true] }, { "name": "structure uescaped LF before string", "valid": false, "json": "[\\u000A\"\"]" }, { "name": "structure unclosed array", "valid": false, "json": "[1" }, { "name": "structure unclosed array partial null", "valid": false, "json": "[ false, nul" }, { "name": "structure unclosed array unfinished false", "valid": false, "json": "[ true, fals" }, { "name": "structure unclosed array unfinished true", "valid": false, "json": "[ false, tru" }, { "name": "structure unclosed object", "valid": false, "json": "{\"asd\":\"asd\"" }, { "name": "structure unicode-identifier", "valid": false, "json": "å" }, { "name": "structure whitespace U+2060 word joiner", "valid": false, "json": "[⁠]" }, { "name": "structure whitespace array", "valid": true, "json": " [] ", "data": [] }, { "name": "structure whitespace formfeed", "valid": false, "json": "[\f]" } ] }, { "suite": "array", "tests": [ { "name": "array 1 true without comma", "valid": false, "json": "[1 true]" }, { "name": "array a invalid utf8", "valid": false, "json": "[a�]" }, { "name": "array arraysWithSpaces", "valid": true, "json": "[[] ]", "data": [[]] }, { "name": "array colon instead of comma", "valid": false, "json": "[\"\": 1]" }, { "name": "array comma after close", "valid": false, "json": "[\"\"]," }, { "name": "array comma and number", "valid": false, "json": "[,1]" }, { "name": "array double comma", "valid": false, "json": "[1,,2]" }, { "name": "array double extra comma", "valid": false, "json": "[\"x\",,]" }, { "name": "array empty", "valid": true, "json": "[]", "data": [] }, { "name": "array empty-string", "valid": true, "json": "[\"\"]", "data": [""] }, { "name": "array ending with newline", "valid": true, "json": "[\"a\"]", "data": ["a"] }, { "name": "array extra close", "valid": false, "json": "[\"x\"]]" }, { "name": "array extra comma", "valid": false, "json": "[\"\",]" }, { "name": "array false", "valid": true, "json": "[false]", "data": [false] }, { "name": "array heterogeneous", "valid": true, "json": "[null, 1, \"1\", {}]", "data": [null, 1, "1", {}] }, { "name": "array incomplete", "valid": false, "json": "[\"x\"" }, { "name": "array incomplete invalid value", "valid": false, "json": "[x" }, { "name": "array inner array no comma", "valid": false, "json": "[3[4]]" }, { "name": "array invalid utf8", "valid": false, "json": "[�]" }, { "name": "array items separated by semicolon", "valid": false, "json": "[1:2]" }, { "name": "array just comma", "valid": false, "json": "[,]" }, { "name": "array just minus", "valid": false, "json": "[-]" }, { "name": "array missing value", "valid": false, "json": "[ , \"\"]" }, { "name": "array newlines unclosed", "valid": false, "json": "[\"a\",\n4\n,1," }, { "name": "array null", "valid": true, "json": "[null]", "data": [null] }, { "name": "array number and comma", "valid": false, "json": "[1,]" }, { "name": "array number and several commas", "valid": false, "json": "[1,,]" }, { "name": "array spaces vertical tab formfeed", "valid": false, "json": "[\"\u000ba\"\\f]" }, { "name": "array star inside", "valid": false, "json": "[*]" }, { "name": "array unclosed", "valid": false, "json": "[\"\"" }, { "name": "array unclosed trailing comma", "valid": false, "json": "[1," }, { "name": "array unclosed with new lines", "valid": false, "json": "[1,\n1\n,1" }, { "name": "array unclosed with object inside", "valid": false, "json": "[{}" }, { "name": "array with 1 and newline", "valid": true, "json": "[1\n]", "data": [1] }, { "name": "array with leading space", "valid": true, "json": " [1]", "data": [1] }, { "name": "array with several null", "valid": true, "json": "[1,null,null,null,2]", "data": [1, null, null, null, 2] }, { "name": "array with trailing space", "valid": true, "json": "[2] ", "data": [2] } ] }, { "suite": "incomplete", "tests": [ { "name": "incomplete false", "valid": false, "json": "[fals]" }, { "name": "incomplete null", "valid": false, "json": "[nul]" }, { "name": "incomplete true", "valid": false, "json": "[tru]" } ] }, { "suite": "multidigit", "tests": [ { "name": "multidigit number then 00", "valid": false, "json": "123\u0000" } ] }, { "suite": "single", "tests": [ { "name": "single space", "valid": false, "json": " " } ] } ] ================================================ FILE: spec/jtd-schema.spec.ts ================================================ import type AjvJTD from "../dist/jtd" import type {SchemaObject, JTDParser} from "../dist/jtd" import _AjvJTD from "./ajv_jtd" import getAjvInstances from "./ajv_instances" import {withStandalone} from "./ajv_standalone" import jtdValidationTests = require("./json-typedef-spec/tests/validation.json") import jtdInvalidSchemasTests = require("./json-typedef-spec/tests/invalid_schemas.json") // tests from https://github.com/nst/JSONTestSuite import jsonParseTests = require("./json_parse_tests.json") import assert = require("assert") import AjvPack from "../dist/standalone/instance" interface TestCase { schema: SchemaObject instance: unknown errors: TestCaseError[] } interface TestCaseError { instancePath: string[] schemaPath: string[] } interface JSONParseTest { name: string valid: boolean | null json: string data?: unknown only?: boolean skip?: boolean } interface JSONParseTestSuite { suite: string tests: JSONParseTest[] } interface JTDError { instancePath: string schemaPath: string } // const ONLY: RegExp[] = [ // "empty", // "ref", // "type", // "enum", // "elements", // "properties", // "optionalProperties", // "discriminator", // "values", // ].map((s) => new RegExp(`(^|.*\\s)${s}\\s.*-`)) const ONLY: RegExp[] = [] describe("JSON Type Definition", () => { describe("validation", function () { this.timeout(10000) let ajvs: AjvJTD[] before(() => { ajvs = getAjvInstances(_AjvJTD, { allErrors: true, inlineRefs: false, code: {es5: true, lines: true, optimize: false}, }) as AjvJTD[] ajvs.forEach((ajv) => (ajv.opts.code.source = true)) }) for (const testName in jtdValidationTests) { const {schema, instance, errors} = jtdValidationTests[testName] as TestCase const valid = errors.length === 0 describeOnly(testName, () => it(`should be ${valid ? "valid" : "invalid"}`, () => withStandalone(ajvs).forEach((ajv) => { // console.log(ajv.compile(schema).toString()) // console.log(ajv.validate(schema, instance), ajv.errors) assert.strictEqual(ajv.validate(schema, instance), valid) const opts = ajv instanceof AjvPack ? ajv.ajv.opts : ajv.opts if (opts.allErrors) { assert.deepStrictEqual(cleanErrors(ajv.errors), valid ? null : convertErrors(errors)) } })) ) } function cleanErrors(errors?: JTDError[] | null): JTDError[] | null | undefined { if (errors) { return sortErrors(errors.map(({instancePath, schemaPath}) => ({instancePath, schemaPath}))) } return errors } function convertErrors(errors: TestCaseError[]): JTDError[] | null | undefined { return sortErrors( errors.map((e) => ({ instancePath: jsonPointer(e.instancePath), schemaPath: jsonPointer(e.schemaPath), })) ) } function sortErrors(errors?: JTDError[] | null): JTDError[] | null | undefined { if (errors) { errors.sort( (e1: JTDError, e2: JTDError) => e1.schemaPath.localeCompare(e2.schemaPath) || e1.instancePath.localeCompare(e2.instancePath) ) } return errors } function jsonPointer(error: string[]): string { return error.map((s) => `/${s}`).join("") } }) describe("invalid schemas", () => { let ajv: AjvJTD before(() => (ajv = new _AjvJTD())) for (const testName in jtdInvalidSchemasTests) { const schema = jtdInvalidSchemasTests[testName] describe(testName, () => it("should be invalid schema", () => assert.throws(() => ajv.compile(schema))) ) } }) describe("serialize", () => { const ajv = new _AjvJTD() for (const testName in jtdValidationTests) { const {schema, instance, errors} = jtdValidationTests[testName] as TestCase const valid = errors.length === 0 if (!valid) continue describe(testName, () => it(`should serialize data`, () => { const serialize = ajv.compileSerializer(schema) // console.log(serialize.toString()) assert.deepStrictEqual(JSON.parse(serialize(instance)), instance) }) ) } }) describe("serialize special numeric values", () => { describe("fast", () => { const ajv = new _AjvJTD({specialNumbers: "fast"}) it(`should serialize Infinity to literal`, () => { const serialize = ajv.compileSerializer({type: "float64"}) const res = serialize(Infinity) assert.equal(res, "Infinity") assert.throws(() => JSON.parse(res)) }) it(`should serialize -Infinity to literal`, () => { const serialize = ajv.compileSerializer({type: "float64"}) const res = serialize(-Infinity) assert.equal(res, "-Infinity") assert.throws(() => JSON.parse(res)) }) it(`should serialize NaN to literal`, () => { const serialize = ajv.compileSerializer({type: "float64"}) const res = serialize(NaN) assert.equal(res, "NaN") assert.throws(() => JSON.parse(res)) }) }) describe("to null", () => { const ajv = new _AjvJTD({specialNumbers: "null"}) it(`should serialize Infinity to null`, () => { const serialize = ajv.compileSerializer({type: "float64"}) const res = serialize(Infinity) assert.equal(res, "null") assert.equal(JSON.parse(res), null) }) it(`should serialize -Infinity to null`, () => { const serialize = ajv.compileSerializer({type: "float64"}) const res = serialize(-Infinity) assert.equal(res, "null") assert.equal(JSON.parse(res), null) }) it(`should serialize NaN to null`, () => { const serialize = ajv.compileSerializer({type: "float64"}) const res = serialize(NaN) assert.equal(res, "null") assert.equal(JSON.parse(res), null) }) }) }) describe("parse", () => { let ajv: AjvJTD before(() => (ajv = new _AjvJTD())) for (const testName in jtdValidationTests) { const {schema, instance, errors} = jtdValidationTests[testName] as TestCase const valid = errors.length === 0 describeOnly(testName, () => { if (valid) { it(`should parse valid JSON string`, () => { const parse = ajv.compileParser(schema) // console.log(schema, instance, `"${JSON.stringify(instance)}"`, parse.toString()) shouldParse(parse, JSON.stringify(instance), instance) shouldParse(parse, ` ${JSON.stringify(instance, null, 2)} `, instance) }) } else { it(`should return undefined on invalid JSON string`, () => { const parse = ajv.compileParser(schema) // console.log(parse.toString()) shouldFail(parse, JSON.stringify(instance)) shouldFail(parse, ` ${JSON.stringify(instance, null, 2)} `) }) } }) } }) describe("parse tests nst/JSONTestSuite", () => { const ajv = new _AjvJTD() const parseJson: JTDParser = ajv.compileParser({}) const parse: {[K in "string" | "number" | "array" | "object"]: JTDParser} = { string: ajv.compileParser({elements: {type: "string"}}), number: ajv.compileParser({elements: {type: "float64"}}), array: ajv.compileParser({elements: {}}), object: ajv.compileParser({values: {}}), } for (const {suite, tests} of jsonParseTests as JSONParseTestSuite[]) { describe(suite, () => { for (const test of tests) { const {valid, name, json, data} = test if (valid) { it(`should parse ${name}`, () => shouldParse(parseJson, json, data)) if (suite in parse) { _it(test)(`should parse as ${suite}: ${name}`, () => shouldParse(parse[suite], json, data) ) } } else if (valid === false) { it(`should fail parsing ${name}`, () => shouldFail(parseJson, json)) if (suite in parse) { _it(test)(`should fail parsing as ${suite}: ${name}`, () => shouldFail(parse[suite], json) ) } } } }) } }) }) type TestFunc = typeof it | typeof it.only | typeof it.skip function _it({only, skip}: JSONParseTest): TestFunc { return skip ? it.skip : only ? it.only : it } function shouldParse(parse: JTDParser, str: string, res: unknown): void { assert.deepStrictEqual(parse(str), res) assert.strictEqual(parse.message, undefined) assert.strictEqual(parse.position, undefined) } function shouldFail(parse: JTDParser, str: string): void { assert.strictEqual(parse(str), undefined) assert.strictEqual(typeof parse.message, "string") assert.strictEqual(typeof parse.position, "number") } function describeOnly(name: string, func: () => void) { if (ONLY.length === 0 || ONLY.some((p) => p.test(name))) { describe(name, func) } else { describe.skip(name, func) } } ================================================ FILE: spec/jtd-timestamps.spec.ts ================================================ import _AjvJTD from "./ajv_jtd" import assert = require("assert") import type {JTDOptions, JTDSchemaType} from "../dist/jtd" describe("JTD timestamps", function () { this.timeout(10000) describe("validation", () => { it("should accept dates or strings by default", () => { testTimestamp({}, {Date: true, datetime: true, date: false}) }) it("timestamp: string should accept only strings", () => { testTimestamp({timestamp: "string"}, {Date: false, datetime: true, date: false}) }) it("timestamp: date should accept only Date objects", () => { testTimestamp({timestamp: "date"}, {Date: true, datetime: false, date: false}) }) it("allowDate: true should accept date without time component", () => { testTimestamp({allowDate: true}, {Date: true, datetime: true, date: true}) testTimestamp( {allowDate: true, timestamp: "string"}, {Date: false, datetime: true, date: true} ) testTimestamp( {allowDate: true, timestamp: "date"}, {Date: true, datetime: false, date: false} ) }) function testTimestamp( opts: JTDOptions, valid: {Date: boolean; datetime: boolean; date: boolean} ) { const ajv = new _AjvJTD(opts) const schema = {type: "timestamp"} const validate = ajv.compile(schema) assert.strictEqual(validate(new Date()), valid.Date) assert.strictEqual(validate("2021-05-03T05:24:43.906Z"), valid.datetime) assert.strictEqual(validate("2021-05-03"), valid.date) assert.strictEqual(validate("foo"), false) } }) describe("parseDate option", () => { it("should parse timestamp as Date object", () => { const schema: JTDSchemaType = {type: "timestamp"} const ajv = new _AjvJTD({parseDate: true}) const parseTS = ajv.compileParser(schema) assert.strictEqual( parseTS('"2021-05-14T17:59:03.851Z"')?.toISOString(), "2021-05-14T17:59:03.851Z" ) assert.strictEqual(parseTS('"2021-05-14"')?.toISOString(), undefined) }) it("allowDate: true should parse timestamp and date as Date objects", () => { const schema: JTDSchemaType = {type: "timestamp"} const ajv = new _AjvJTD({parseDate: true, allowDate: true}) const parseTS = ajv.compileParser(schema) assert.strictEqual( parseTS('"2021-05-14T17:59:03.851Z"')?.toISOString(), "2021-05-14T17:59:03.851Z" ) assert.strictEqual(parseTS('"2021-05-14"')?.toISOString(), "2021-05-14T00:00:00.000Z") }) }) describe("serializing Date objects", () => { it("should serialize Date as JSON string", () => { const schema: JTDSchemaType = {type: "timestamp"} const ajv = new _AjvJTD() const serializeTS = ajv.compileSerializer(schema) assert.strictEqual( serializeTS(new Date("2021-05-14T17:59:03.851Z")), '"2021-05-14T17:59:03.851Z"' ) }) }) }) ================================================ FILE: spec/keyword.spec.ts ================================================ import type {ErrorObject, SchemaObject, SchemaValidateFunction} from "../lib/types" import type AjvCore from "../dist/core" // currently most tests include compiled code, if any code re-compiled locally, instanceof would fail import {_, nil} from "../dist/compile/codegen/code" import getAjvAllInstances from "./ajv_all_instances" import _Ajv from "./ajv" import equal from "../dist/runtime/equal" import assert = require("assert") import chai from "./chai" const should = chai.should() describe("User-defined keywords", () => { let ajv: AjvCore, instances: AjvCore[] beforeEach(() => { instances = getAjvAllInstances( { allErrors: true, verbose: true, inlineRefs: false, }, {allowUnionTypes: true} ) ajv = instances[0] }) describe("user-defined keyword", () => { describe('keyword with "validate" function', () => { it("should add and validate keyword", () => { testEvenKeyword({keyword: "x-even", type: "number", validate: validateEven}) function validateEven(schema, data) { if (typeof schema != "boolean") { throw new Error('The value of "even" keyword must be boolean') } return data % 2 ? !schema : schema } }) it("should add, validate keyword schema and validate rule", () => { testEvenKeyword({ keyword: "x-even", type: "number", validate: validateEven, metaSchema: {type: "boolean"}, }) shouldBeInvalidSchema({type: "number", "x-even": "not_boolean"}) function validateEven(schema, data) { return data % 2 ? !schema : schema } }) it('should pass parent schema to "interpreted" keyword validation', () => { testRangeKeyword({ keyword: "x-range", type: "number", validate: validateRange, }) function validateRange(schema, data, parentSchema) { validateRangeSchema(schema, parentSchema) return parentSchema.exclusiveRange === true ? data > schema[0] && data < schema[1] : data >= schema[0] && data <= schema[1] } }) it('should validate meta schema and pass parent schema to "interpreted" keyword validation', () => { testRangeKeyword({ keyword: "x-range", type: "number", validate: validateRange, metaSchema: { type: "array", items: [{type: "number"}, {type: "number"}], minItems: 2, additionalItems: false, }, }) shouldBeInvalidSchema({type: "number", "x-range": ["1", 2]}) shouldBeInvalidSchema({type: "number", "x-range": {}}) shouldBeInvalidSchema({type: "number", "x-range": [1, 2, 3]}) function validateRange(schema, data, parentSchema) { return parentSchema.exclusiveRange === true ? data > schema[0] && data < schema[1] : data >= schema[0] && data <= schema[1] } }) it('should allow defining errors for "validate" keyword', () => { const validateRange: SchemaValidateFunction = _validateRange testRangeKeyword({keyword: "x-range", type: "number", validate: validateRange}, true) function _validateRange(schema, data, parentSchema) { validateRangeSchema(schema, parentSchema) const min = schema[0], max = schema[1], exclusive = parentSchema.exclusiveRange === true const minOk = exclusive ? data > min : data >= min const maxOk = exclusive ? data < max : data <= max const valid = minOk && maxOk if (!valid) { const err: Partial = {keyword: "x-range"} validateRange.errors = [err] let comparison, limit if (minOk) { comparison = exclusive ? "<" : "<=" limit = max } else { comparison = exclusive ? ">" : ">=" limit = min } err.message = "should be " + comparison + " " + limit err.params = { comparison: comparison, limit: limit, exclusive: exclusive, } } return valid } }) it("should support schemaType", () => { testEvenKeyword({ keyword: "x-even", type: "number", schemaType: "boolean", validate: (schema, data) => (data % 2 ? !schema : schema), }) }) }) describe('keyword with "compile" function', () => { it("should add and validate keyword", () => { testEvenKeyword({ keyword: "x-even", type: "number", compile: compileEven, }) shouldBeInvalidSchema( { type: "number", "x-even": "not_boolean", }, 'The value of "x-even" keyword must be boolean' ) function compileEven(schema) { if (typeof schema != "boolean") { throw new Error('The value of "x-even" keyword must be boolean') } return schema ? isEven : isOdd } function isEven(data) { return data % 2 === 0 } function isOdd(data) { return data % 2 !== 0 } }) it("should add, validate keyword schema and validate rule", () => { testEvenKeyword({ keyword: "x-even", type: "number", compile: compileEven, metaSchema: {type: "boolean"}, }) shouldBeInvalidSchema({ type: "number", "x-even": "not_boolean", }) function compileEven(schema) { return schema ? isEven : isOdd } function isEven(data) { return data % 2 === 0 } function isOdd(data) { return data % 2 !== 0 } }) it("should compile keyword validating function only once per schema", () => { testConstantKeyword({keyword: "myConstant", compile: compileConstant}) }) it("should allow multiple schemas for the same keyword", () => { testMultipleConstantKeyword({keyword: "x-constant", compile: compileConstant}) }) it('should pass parent schema to "compiled" keyword validation', () => { testRangeKeyword({keyword: "x-range", type: "number", compile: compileRange}) }) it("should allow multiple parent schemas for the same keyword", () => { testMultipleRangeKeyword({keyword: "x-range", type: "number", compile: compileRange}) }) it("should support schemaType", () => { testEvenKeyword({ keyword: "x-even", type: "number", schemaType: "boolean", compile: compileEven, }) shouldBeInvalidSchema( { type: "number", "x-even": "not_boolean", }, 'x-even value must be ["boolean"]' ) function compileEven(schema) { if (schema) return (data) => data % 2 === 0 return (data) => data % 2 !== 0 } }) }) function compileConstant(schema) { return typeof schema == "object" && schema !== null ? isDeepEqual : isStrictEqual function isDeepEqual(data) { return equal(data, schema) } function isStrictEqual(data) { return data === schema } } function compileRange(schema, parentSchema) { validateRangeSchema(schema, parentSchema) const min = schema[0] const max = schema[1] return parentSchema.exclusiveRange === true ? (data) => data > min && data < max : (data) => data >= min && data <= max } }) describe("macro keywords", () => { it('should add and validate keywords with "macro" function', () => { testEvenKeyword({keyword: "x-even", type: "number", macro: macroEven}, 2) }) it("should add and expand macro rule", () => { testConstantKeyword({keyword: "myConstant", macro: macroConstant}, 2) }) it("should allow multiple schemas for the same macro keyword", () => { testMultipleConstantKeyword({keyword: "x-constant", macro: macroConstant}, 2) }) it('should pass parent schema to "macro" keyword', () => { testRangeKeyword({keyword: "x-range", type: "number", macro: macroRange}, undefined, 2) }) it("should allow multiple parent schemas for the same macro keyword", () => { testMultipleRangeKeyword({keyword: "x-range", type: "number", macro: macroRange}, 2) }) it("should support resolving $ref without id or $id", () => { instances.forEach((_ajv) => { _ajv.addKeyword({ keyword: "macroRef", macro(schema, _parentSchema, it) { it.baseId.should.equal("#") const ref = schema.$ref const validate = _ajv.getSchema(ref) if (!validate) throw new _Ajv.MissingRefError(_ajv.opts.uriResolver, it.baseId, ref) return validate.schema }, metaSchema: { type: "object", required: ["$ref"], additionalProperties: false, properties: { $ref: { type: "string", }, }, }, }) const schema = { macroRef: { $ref: "#/definitions/schema", }, definitions: { schema: { type: "string", }, }, } const validate = _ajv.compile(schema) shouldBeValid(validate, "foo") shouldBeInvalid(validate, 1, 2) }) }) it("should recursively expand macro keywords", () => { instances.forEach((_ajv) => { _ajv.addKeyword({ keyword: "deepProperties", type: "object", macro: macroDeepProperties, }) _ajv.addKeyword({keyword: "range", type: "number", macro: macroRange}) const schema = { type: "object", deepProperties: { "a.b.c": {type: "number", range: [2, 4]}, "d.e.f.g": {type: "string"}, }, } /* This schema recursively expands to: { "allOf": [ { "properties": { "a": { "properties": { "b": { "properties": { "c": { "type": "number", "minimum": 2, "exclusiveMinimum": false, "maximum": 4, "exclusiveMaximum": false } } } } } } }, { "properties": { "d": { "properties": { "e": { "properties": { "f": { "properties": { "g": { "type": "string" } } } } } } } } } ] } */ const validate = _ajv.compile(schema) shouldBeValid(validate, { a: {b: {c: 3}}, d: {e: {f: {g: "foo"}}}, }) shouldBeInvalid( validate, { a: {b: {c: 5}}, // out of range d: {e: {f: {g: "foo"}}}, }, 5 ) shouldBeInvalid( validate, { a: {b: {c: "bar"}}, // not number d: {e: {f: {g: "foo"}}}, }, 4 ) shouldBeInvalid( validate, { a: {b: {c: 3}}, d: {e: {f: {g: 2}}}, // not string }, 5 ) function macroDeepProperties(_schema) { if (typeof _schema != "object") { throw new Error("schema of deepProperty should be an object") } const expanded: any[] = [] for (const prop in _schema) { const path = prop.split(".") const properties = {} if (path.length === 1) { properties[prop] = _schema[prop] } else { const deepProperties = {} deepProperties[path.slice(1).join(".")] = _schema[prop] properties[path[0]] = {type: "object", deepProperties} } expanded.push({type: "object", properties}) } return expanded.length === 1 ? expanded[0] : {allOf: expanded} } }) }) it("should correctly expand multiple macros on the same level", () => { instances.forEach((_ajv) => { _ajv.addKeyword({keyword: "range", type: "number", macro: macroRange}) _ajv.addKeyword({keyword: "even", type: "number", macro: macroEven}) const schema = { type: "number", range: [4, 6], even: true, } const validate = _ajv.compile(schema) const numErrors = _ajv.opts.allErrors ? 4 : 2 shouldBeInvalid(validate, 2, 2) shouldBeInvalid(validate, 3, numErrors) shouldBeValid(validate, 4) shouldBeInvalid(validate, 5, 2) shouldBeValid(validate, 6) shouldBeInvalid(validate, 7, numErrors) shouldBeInvalid(validate, 8, 2) }) }) it("should validate macro keyword when it resolves to the same keyword as exists", () => { instances.forEach((_ajv) => { _ajv.addKeyword({keyword: "range", type: "number", macro: macroRange}) const schema = { type: "number", range: [1, 4], minimum: 2.5, } const validate = _ajv.compile(schema) shouldBeValid(validate, 3) shouldBeInvalid(validate, 2) }) }) it("should correctly expand macros in subschemas", () => { instances.forEach((_ajv) => { _ajv.addKeyword({keyword: "range", type: "number", macro: macroRange}) const schema = { type: "number", allOf: [{range: [4, 8]}, {range: [2, 6]}], } const validate = _ajv.compile(schema) shouldBeInvalid(validate, 2, 2) shouldBeInvalid(validate, 3, 2) shouldBeValid(validate, 4) shouldBeValid(validate, 5) shouldBeValid(validate, 6) shouldBeInvalid(validate, 7, 2) shouldBeInvalid(validate, 8, 2) }) }) it("should correctly expand macros in macro expansions", () => { instances.forEach((_ajv) => { _ajv.addKeyword({keyword: "range", type: "number", macro: macroRange}) _ajv.addKeyword({keyword: "exclusiveRange", metaSchema: {type: "boolean"}}) _ajv.addKeyword({keyword: "myContains", type: "array", macro: macroContains}) const schema = { type: "array", myContains: { type: "number", range: [4, 7], exclusiveRange: true, }, } const validate = _ajv.compile(schema) shouldBeInvalid(validate, [1, 2, 3], 2) shouldBeInvalid(validate, [2, 3, 4], 2) shouldBeValid(validate, [3, 4, 5]) // only 5 is in range shouldBeValid(validate, [6, 7, 8]) // only 6 is in range shouldBeInvalid(validate, [7, 8, 9], 2) shouldBeInvalid(validate, [8, 9, 10], 2) function macroContains(_schema) { return {not: {items: {not: _schema}}} } }) }) it("should throw exception if macro expansion is an invalid schema", () => { ajv.addKeyword({keyword: "invalid", macro: macroInvalid}) const schema = {invalid: true} should.throw(() => { ajv.compile(schema) }, /type must be equal to one of the allowed values/) function macroInvalid(/* schema */) { return {type: "invalid"} } }) function macroEven(schema) { if (schema === true) return {multipleOf: 2} if (schema === false) return {not: {multipleOf: 2}} throw new Error('Schema for "even" keyword should be boolean') } function macroConstant(schema /*, parentSchema */) { return {enum: [schema]} } function macroRange(schema, parentSchema) { validateRangeSchema(schema, parentSchema) const exclusive = !!parentSchema.exclusiveRange return exclusive ? {exclusiveMinimum: schema[0], exclusiveMaximum: schema[1]} : {minimum: schema[0], maximum: schema[1]} } }) describe('"code" keywords', () => { it('should add and validate keyword with "code" function', () => { testEvenKeyword({ keyword: "x-even", type: "number", code(cxt) { const {schema, data} = cxt const op = schema ? _`===` : _`!==` cxt.pass(_`${data} % 2 ${op} 0`) }, }) }) it('should pass parent schema to "inline" keyword', () => { testRangeKeyword({ keyword: "x-range", type: "number", code(cxt) { const { schema: [min, max], parentSchema, data, } = cxt const eq = parentSchema.exclusiveRange ? nil : _`=` cxt.pass(_`${data} >${eq} ${min} && ${data} <${eq} ${max}`) }, }) }) it("should allow defining keyword error", () => { testRangeKeyword({ keyword: "x-range", type: "number", code(cxt) { const { gen, schema: [min, max], parentSchema, data, } = cxt const eq = parentSchema.exclusiveRange ? nil : _`=` const minOk = gen.const("minOk", _`${data} >${eq} ${min}`) const maxOk = gen.const("maxOk", _`${data} <${eq} ${max}`) cxt.setParams({minOk, maxOk, eq}) cxt.pass(_`${minOk} && ${maxOk}`) }, error: { message: ({params: {minOk, eq}, schema: [min, max]}) => _`${minOk} ? "should be <${eq} ${max}" : "should be >${eq} ${min}"`, params: ({params: {minOk, eq}, schema: [min, max], parentSchema}) => _`{ comparison: ${minOk} ? "<${eq}" : ">${eq}", limit: ${minOk} ? ${max} : ${min}, exclusive: ${!!parentSchema.exclusiveRange} }`, }, }) }) }) describe('$data reference support with "validate" keywords (with $data option)', () => { beforeEach(() => { instances = getAjvAllInstances( { allErrors: true, verbose: true, inlineRefs: false, }, {$data: true, allowUnionTypes: true} ) ajv = instances[0] }) it('should validate "interpreted" rule', () => { testEvenKeyword$data({ keyword: "x-even-$data", type: "number", $data: true, validate: validateEven, }) function validateEven(schema, data) { if (typeof schema != "boolean") return false return data % 2 ? !schema : schema } }) it('should validate rule with "compile" and "validate" funcs', () => { let compileCalled testEvenKeyword$data({ keyword: "x-even-$data", type: "number", $data: true, compile: compileEven, validate: validateEven, }) compileCalled.should.equal(true) function validateEven(schema, data) { if (typeof schema != "boolean") return false return data % 2 ? !schema : schema } function compileEven(schema) { compileCalled = true if (typeof schema != "boolean") { throw new Error('The value of "even" keyword must be boolean') } return schema ? isEven : isOdd } function isEven(data) { return data % 2 === 0 } function isOdd(data) { return data % 2 !== 0 } }) it('should validate with "compile" and "validate" funcs with meta-schema', () => { let compileCalled testEvenKeyword$data({ keyword: "x-even-$data", type: "number", $data: true, compile: compileEven, validate: validateEven, metaSchema: {type: "boolean"}, }) compileCalled.should.equal(true) shouldBeInvalidSchema({ type: "number", "x-even-$data": "false", }) function validateEven(schema, data) { return data % 2 ? !schema : schema } function compileEven(schema) { compileCalled = true return schema ? isEven : isOdd } function isEven(data) { return data % 2 === 0 } function isOdd(data) { return data % 2 !== 0 } }) it('should validate rule with "macro" and "validate" funcs', () => { let macroCalled testEvenKeyword$data( { keyword: "x-even-$data", type: "number", $data: true, macro: macroEven, validate: validateEven, }, 2 ) macroCalled.should.equal(true) function validateEven(schema, data) { if (typeof schema != "boolean") return false return data % 2 ? !schema : schema } function macroEven(schema) { macroCalled = true if (schema === true) return {multipleOf: 2} if (schema === false) return {not: {multipleOf: 2}} throw new Error('Schema for "even" keyword should be boolean') } }) it('should validate with "macro" and "validate" funcs with meta-schema', () => { let macroCalled testEvenKeyword$data( { keyword: "x-even-$data", type: "number", $data: true, macro: macroEven, validate: validateEven, metaSchema: {type: "boolean"}, }, 2 ) macroCalled.should.equal(true) shouldBeInvalidSchema({ type: "number", "x-even-$data": "false", }) function validateEven(schema, data) { return data % 2 ? !schema : schema } function macroEven(schema): SchemaObject | void { macroCalled = true if (schema === true) return {multipleOf: 2} if (schema === false) return {not: {multipleOf: 2}} } }) it('should validate rule with "code" keyword', () => { testEvenKeyword$data({ keyword: "x-even-$data", type: "number", $data: true, code(cxt) { const {gen, schemaCode: s, data} = cxt gen.if(_`${s} !== undefined`) cxt.pass(_`typeof ${s} == "boolean" && (${data} % 2 ? !${s} : ${s})`) }, }) }) it('should validate with "code" and meta-schema', () => { testEvenKeyword$data({ keyword: "x-even-$data", type: "number", $data: true, code(cxt) { const {gen, schemaCode: s, data} = cxt gen.if(_`${s} !== undefined`) cxt.pass(_`typeof ${s} == "boolean" && (${data} % 2 ? !${s} : ${s})`) }, metaSchema: {type: "boolean"}, }) shouldBeInvalidSchema({ type: "number", "x-even-$data": "false", }) }) it('should fail if "macro" keyword definition has "$data" but no "code" or "validate"', () => { should.throw(() => { ajv.addKeyword({ keyword: "even", type: "number", $data: true, macro: () => { return {} }, }) }, /\$data keyword must have "code" or "validate" function/) }) it("should support schemaType with $data", () => { testEvenKeyword$data({ keyword: "x-even-$data", type: "number", schemaType: "boolean", $data: true, validate: validateEven, }) function validateEven(schema, data) { return data % 2 ? !schema : schema } }) }) function testEvenKeyword(evenDefinition, numErrors = 1) { instances.forEach((_ajv) => { _ajv.addKeyword(evenDefinition) const schema = { type: ["number", "string"], "x-even": true, } const validate = _ajv.compile(schema) shouldBeValid(validate, 2) shouldBeValid(validate, "abc") shouldBeInvalid(validate, 2.5, numErrors) shouldBeInvalid(validate, 3, numErrors) }) } function testEvenKeyword$data(definition, numErrors = 1) { instances.forEach((_ajv) => { _ajv.addKeyword(definition) let schema: any = { type: ["number", "string"], "x-even-$data": true, } let validate = _ajv.compile(schema) shouldBeValid(validate, 2) shouldBeValid(validate, "abc") shouldBeInvalid(validate, 2.5, numErrors) shouldBeInvalid(validate, 3, numErrors) schema = { type: "object", properties: { data: { type: ["number", "string"], "x-even-$data": {$data: "1/evenValue"}, }, evenValue: {}, }, } validate = _ajv.compile(schema) shouldBeValid(validate, {data: 2, evenValue: true}) shouldBeInvalid(validate, {data: 2, evenValue: false}) shouldBeValid(validate, {data: "abc", evenValue: true}) shouldBeValid(validate, {data: "abc", evenValue: false}) shouldBeInvalid(validate, {data: 2.5, evenValue: true}) shouldBeValid(validate, {data: 2.5, evenValue: false}) shouldBeInvalid(validate, {data: 3, evenValue: true}) shouldBeValid(validate, {data: 3, evenValue: false}) shouldBeInvalid(validate, {data: 2, evenValue: "true"}) // valid if the value of x-even-$data keyword is undefined shouldBeValid(validate, {data: 2}) shouldBeValid(validate, {data: 3}) }) } function testConstantKeyword(definition, numErrors?: number) { instances.forEach((_ajv) => { _ajv.addKeyword(definition) const schema = {myConstant: "abc"} const validate = _ajv.compile(schema) shouldBeValid(validate, "abc") shouldBeInvalid(validate, 2, numErrors) shouldBeInvalid(validate, {}, numErrors) }) } function testMultipleConstantKeyword(definition, numErrors?: number) { instances.forEach((_ajv) => { _ajv.addKeyword(definition) const schema = { type: ["object", "array"], properties: { a: {"x-constant": 1}, b: {"x-constant": 1}, }, additionalProperties: {"x-constant": {foo: "bar"}}, items: {"x-constant": {foo: "bar"}}, } const validate = _ajv.compile(schema) shouldBeValid(validate, {a: 1, b: 1}) shouldBeInvalid(validate, {a: 2, b: 1}, numErrors) shouldBeValid(validate, {a: 1, c: {foo: "bar"}}) shouldBeInvalid(validate, {a: 1, c: {foo: "baz"}}, numErrors) shouldBeValid(validate, [{foo: "bar"}]) shouldBeValid(validate, [{foo: "bar"}, {foo: "bar"}]) shouldBeInvalid(validate, [1], numErrors) }) } function testRangeKeyword(definition, createsErrors?: boolean, numErrors?: number) { instances.forEach((_ajv) => { _ajv.addKeyword(definition) _ajv.addKeyword({keyword: "exclusiveRange", schemaType: "boolean"}) let schema: SchemaObject = { type: ["number", "string"], "x-range": [2, 4], } let validate = _ajv.compile(schema) shouldBeValid(validate, 2) shouldBeValid(validate, 3) shouldBeValid(validate, 4) shouldBeValid(validate, "abc") shouldBeInvalid(validate, 1.99, numErrors) if (createsErrors) { shouldBeRangeError(validate.errors?.[0], "", "#/x-range", ">=", 2) } shouldBeInvalid(validate, 4.01, numErrors) if (createsErrors) { shouldBeRangeError(validate.errors?.[0], "", "#/x-range", "<=", 4) } schema = { type: "object", properties: { foo: { type: ["number"], "x-range": [2, 4], exclusiveRange: true, }, }, } validate = _ajv.compile(schema) shouldBeValid(validate, {foo: 2.01}) shouldBeValid(validate, {foo: 3}) shouldBeValid(validate, {foo: 3.99}) shouldBeInvalid(validate, {foo: 2}, numErrors) if (createsErrors) { shouldBeRangeError(validate.errors?.[0], "/foo", "#/properties/foo/x-range", ">", 2, true) } shouldBeInvalid(validate, {foo: 4}, numErrors) if (createsErrors) { shouldBeRangeError(validate.errors?.[0], "/foo", "#/properties/foo/x-range", "<", 4, true) } }) } function testMultipleRangeKeyword(definition, numErrors?: number) { instances.forEach((_ajv) => { _ajv.opts.strictTypes = false _ajv.addKeyword(definition) _ajv.addKeyword({keyword: "exclusiveRange", schemaType: "boolean"}) const schema = { properties: { a: {type: "number", "x-range": [2, 4], exclusiveRange: true}, b: {type: "number", "x-range": [2, 4], exclusiveRange: false}, }, additionalProperties: {type: "number", "x-range": [5, 7]}, items: {type: "number", "x-range": [5, 7]}, } const validate = _ajv.compile(schema) shouldBeValid(validate, {a: 3.99, b: 4}) shouldBeInvalid(validate, {a: 4, b: 4}, numErrors) shouldBeValid(validate, {a: 2.01, c: 7}) shouldBeInvalid(validate, {a: 2.01, c: 7.01}, numErrors) shouldBeValid(validate, [5, 6, 7]) shouldBeInvalid(validate, [7.01], numErrors) }) } function shouldBeRangeError( error, instancePath, schemaPath, comparison, limit, exclusive?: boolean ) { delete error.schema delete error.data error.should.eql({ keyword: "x-range", instancePath, schemaPath, message: "should be " + comparison + " " + limit, params: { comparison: comparison, limit: limit, exclusive: !!exclusive, }, }) } function validateRangeSchema(schema, parentSchema) { const schemaValid = Array.isArray(schema) && schema.length === 2 && typeof schema[0] == "number" && typeof schema[1] == "number" if (!schemaValid) { throw new Error("Invalid schema for range keyword, should be array of 2 numbers") } const exclusiveRangeSchemaValid = parentSchema.exclusiveRange === undefined || typeof parentSchema.exclusiveRange == "boolean" if (!exclusiveRangeSchemaValid) { throw new Error("Invalid schema for exclusiveRange keyword, should be boolean") } } function shouldBeValid(validate, data) { validate(data).should.equal(true) should.not.exist(validate.errors) } function shouldBeInvalid(validate, data, numErrors = 1) { validate(data).should.equal(false) validate.errors.should.have.length(numErrors) } function shouldBeInvalidSchema( schema, msg: string | RegExp = /keyword .+ value is invalid at path/ ) { instances.forEach((_ajv) => { should.throw(() => { _ajv.compile(schema) }, msg) }) } describe("addKeyword method", () => { const TEST_TYPES = [undefined, "number", "string", "boolean", ["number", "string"]] it("should throw if defined keyword is passed", () => { testThrow(["minimum", "maximum", "multipleOf", "minLength", "maxLength"]) testThrowDuplicate("user-defined") function testThrow(keywords) { TEST_TYPES.forEach((dataType, index) => { should.throw(() => { _addKeyword(keywords[index], dataType) }, /already defined/) }) } function testThrowDuplicate(keywordPrefix) { let index = 0 TEST_TYPES.forEach((dataType1) => { TEST_TYPES.forEach((dataType2) => { const keyword = keywordPrefix + index++ _addKeyword(keyword, dataType1) should.throw(() => { _addKeyword(keyword, dataType2) }, /already defined/) }) }) } }) it("should throw if keyword is not a valid name", () => { should.not.throw(() => { ajv.addKeyword("mykeyword") }) should.not.throw(() => { ajv.addKeyword("hyphens-are-valid") }) should.not.throw(() => { ajv.addKeyword("colons:are-valid") }) should.throw(() => { ajv.addKeyword("single-'quote-not-valid") }, /invalid name/) should.throw(() => { ajv.addKeyword("3-start-with-number-not-valid") }, /invalid name/) should.throw(() => { ajv.addKeyword("-start-with-hyphen-not-valid") }, /invalid name/) should.throw(() => { ajv.addKeyword("spaces not valid") }, /invalid name/) }) it("should return instance of itself", () => { const res = ajv.addKeyword("any") res.should.equal(ajv) }) it("should throw if unknown type is passed", () => { should.throw(() => { _addKeyword("user-defined1", "wrongtype") }, /type must be JSONType/) should.throw(() => { _addKeyword("user-defined2", ["number", "wrongtype"]) }, /type must be JSONType/) should.throw(() => { _addKeyword("user-defined3", ["number", undefined]) }, /type must be JSONType/) }) it("should support old API addKeyword", () => { ajv = new _Ajv({logger: false}) //@ts-expect-error ajv.addKeyword("min", { type: "number", schemaType: "number", validate: (schema, data) => data >= schema, }) const validate = ajv.compile({ type: "number", min: 0, }) validate(1).should.equal(true) validate(-1).should.equal(false) }) function _addKeyword(keyword, dataType) { ajv.addKeyword({ keyword, type: dataType, validate: () => true, }) } }) describe("getKeyword", () => { // TODO update this test it("should return false for unknown keywords", () => { ajv.getKeyword("unknown").should.equal(false) }) // TODO change to account for pre-defined keywords with definitions it("should return keyword definition", () => { const definition = { keyword: "mykeyword", validate: () => true, } ajv.addKeyword(definition) const def = ajv.getKeyword("mykeyword") assert(typeof def == "object") def.keyword.should.equal("mykeyword") }) }) describe("removeKeyword", () => { it("should remove and allow redefining keyword", () => { ajv = new _Ajv({strict: false}) ajv.addKeyword({ keyword: "positive", type: "number", validate: (_schema, data) => data > 0, }) const schema = {type: "number", positive: true} let validate = ajv.compile(schema) validate(0).should.equal(false) validate(1).should.equal(true) should.throw(() => { ajv.addKeyword({ keyword: "positive", type: "number", validate: function (_sch, data) { return data >= 0 }, }) }, /already defined/) ajv.removeKeyword("positive") ajv.removeSchema(schema) validate = ajv.compile(schema) validate(-1).should.equal(true) ajv.removeSchema(schema) ajv.addKeyword({ keyword: "positive", type: "number", validate: function (_sch, data) { return data >= 0 }, }) validate = ajv.compile(schema) validate(-1).should.equal(false) validate(0).should.equal(true) validate(1).should.equal(true) }) it("should remove and allow redefining standard keyword", () => { ajv = new _Ajv({strict: false}) const schema = {minimum: 1} let validate = ajv.compile(schema) validate(0).should.equal(false) validate(1).should.equal(true) validate(2).should.equal(true) ajv.removeKeyword("minimum") ajv.removeSchema(schema) validate = ajv.compile(schema) validate(0).should.equal(true) validate(1).should.equal(true) validate(2).should.equal(true) ajv.addKeyword({ keyword: "minimum", type: "number", // make minimum exclusive validate: (sch, data) => data > sch, }) ajv.removeSchema(schema) validate = ajv.compile(schema) validate(0).should.equal(false) validate(1).should.equal(false) validate(2).should.equal(true) }) it("should return instance of itself", () => { const res = ajv.addKeyword("any").removeKeyword("any") res.should.equal(ajv) }) }) describe("user-defined keywords mutating data", () => { it("should NOT update data without option modifying", () => { should.throw(() => { testModifying(false) }, /expected false to equal true/) }) it("should update data with option modifying", () => { testModifying(true) }) function testModifying(withOption) { const collectionFormat = { csv: function (data, {parentData, parentDataProperty}) { parentData[parentDataProperty] = data.split(",") return true }, } ajv.addKeyword({ keyword: "collectionFormat", type: "string", modifying: withOption, compile: function (schema) { return collectionFormat[schema] }, metaSchema: { enum: ["csv"], }, }) const validate = ajv.compile({ type: "object", properties: { foo: { allOf: [ { type: "string", collectionFormat: "csv", }, { type: "array", items: {type: "string"}, }, ], }, }, additionalProperties: false, }) const obj: any = {foo: "bar,baz,quux"} validate(obj).should.equal(true) obj.should.eql({foo: ["bar", "baz", "quux"]}) } }) describe('"validate" keywords with predefined validation result', () => { it("should ignore result from validation function", () => { ajv.addKeyword({ keyword: "pass", validate: () => false, valid: true, }) ajv.addKeyword({ keyword: "fail", validate: () => true, valid: false, }) ajv.validate({pass: ""}, 1).should.equal(true) ajv.validate({fail: ""}, 1).should.equal(false) }) }) describe('"dependencies" in keyword definition', () => { it("should require properties in the parent schema", () => { ajv.addKeyword({ keyword: "allRequired", type: "object", macro: (schema, parentSchema) => schema ? {required: Object.keys(parentSchema.properties)} : true, schemaType: "boolean", dependencies: ["properties"], }) const invalidSchema = { type: "object", allRequired: true, } should.throw(() => { ajv.compile(invalidSchema) }, /parent schema must have dependencies of allRequired: properties/) const schema = { type: "object", properties: { foo: true, }, allRequired: true, } const v = ajv.compile(schema) v({foo: 1}).should.equal(true) v({}).should.equal(false) }) }) }) ================================================ FILE: spec/options/comment.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("$comment option", () => { describe("= true", () => { let logCalls: any[][] function log(...args: any[]) { logCalls.push(args) } const logger = {log, warn: log, error: log} it("should log the text from $comment keyword", () => { const schema = { $comment: "object root", type: "object", properties: { foo: {$comment: "property foo"}, bar: {$comment: "property bar", type: "integer"}, }, } const ajv = new _Ajv({$comment: true, logger}) const fullAjv = new _Ajv({allErrors: true, $comment: true, logger}) ;[ajv, fullAjv].forEach((_ajv) => { const validate = _ajv.compile(schema) test({}, true, [["object root"]]) test({foo: 1}, true, [["object root"], ["property foo"]]) test({foo: 1, bar: 2}, true, [["object root"], ["property foo"], ["property bar"]]) test({foo: 1, bar: "baz"}, false, [["object root"], ["property foo"], ["property bar"]]) function test(data, valid, expectedLogCalls) { logCalls = [] validate(data).should.equal(valid) logCalls.should.eql(expectedLogCalls) } }) }) }) describe("function hook", () => { let hookCalls function hook(...args: any[]) { hookCalls.push(Array.prototype.slice.call(args)) } it("should pass the text from $comment keyword to the hook", () => { const schema = { $comment: "object root", type: "object", properties: { foo: {$comment: "property foo"}, bar: {$comment: "property bar", type: "integer"}, }, } const ajv = new _Ajv({$comment: hook}) const fullAjv = new _Ajv({allErrors: true, $comment: hook}) ;[ajv, fullAjv].forEach((_ajv) => { const validate = _ajv.compile(schema) test({}, true, [["object root", "#/$comment", schema]]) test({foo: 1}, true, [ ["object root", "#/$comment", schema], ["property foo", "#/properties/foo/$comment", schema], ]) test({foo: 1, bar: 2}, true, [ ["object root", "#/$comment", schema], ["property foo", "#/properties/foo/$comment", schema], ["property bar", "#/properties/bar/$comment", schema], ]) test({foo: 1, bar: "baz"}, false, [ ["object root", "#/$comment", schema], ["property foo", "#/properties/foo/$comment", schema], ["property bar", "#/properties/bar/$comment", schema], ]) function test(data, valid, expectedHookCalls) { hookCalls = [] validate(data).should.equal(valid) hookCalls.should.eql(expectedHookCalls) } }) }) }) }) ================================================ FILE: spec/options/int32range.spec.ts ================================================ import _AjvJTD from "../ajv_jtd" import assert = require("assert") describe("JTD int32range option", function () { this.timeout(10000) describe("validation", () => { it("should limit range for int32 and uint32 types by default", () => { const ajv = new _AjvJTD() const validateInt32 = ajv.compile({type: "int32"}) assert.strictEqual(validateInt32(-2147483648), true) assert.strictEqual(validateInt32(-2147483649), false) assert.strictEqual(validateInt32(2147483647), true) assert.strictEqual(validateInt32(2147483648), false) const validateUint32 = ajv.compile({type: "uint32"}) assert.strictEqual(validateUint32(0), true) assert.strictEqual(validateUint32(-1), false) assert.strictEqual(validateUint32(4294967295), true) assert.strictEqual(validateUint32(4294967296), false) }) it("should NOT limit range for int32 and uint32 types with int32range: false", () => { const ajv = new _AjvJTD({int32range: false}) const validateInt32 = ajv.compile({type: "int32"}) assert.strictEqual(validateInt32(-2147483648), true) assert.strictEqual(validateInt32(-2147483649), true) assert.strictEqual(validateInt32(Number.MIN_SAFE_INTEGER), true) assert.strictEqual(validateInt32(2147483647), true) assert.strictEqual(validateInt32(2147483648), true) assert.strictEqual(validateInt32(Number.MAX_SAFE_INTEGER), true) const validateUint32 = ajv.compile({type: "uint32"}) assert.strictEqual(validateUint32(0), true) assert.strictEqual(validateUint32(-1), false) assert.strictEqual(validateUint32(4294967295), true) assert.strictEqual(validateUint32(4294967296), true) assert.strictEqual(validateUint32(Number.MAX_SAFE_INTEGER), true) }) }) describe("parsing", () => { it("should limit range for int32 and uint32 types by default", () => { const ajv = new _AjvJTD() const parseInt32 = ajv.compileParser({type: "int32"}) assert.strictEqual(parseInt32("-2147483648"), -2147483648) assert.strictEqual(parseInt32("-2147483649"), undefined) assert.strictEqual(parseInt32("2147483647"), 2147483647) assert.strictEqual(parseInt32("2147483648"), undefined) const parseUint32 = ajv.compileParser({type: "uint32"}) assert.strictEqual(parseUint32("0"), 0) assert.strictEqual(parseUint32("-1"), undefined) assert.strictEqual(parseUint32("4294967295"), 4294967295) assert.strictEqual(parseUint32("4294967296"), undefined) }) it("should NOT limit range for int32 and uint32 types with int32range: false", () => { const ajv = new _AjvJTD({int32range: false}) const parseInt32 = ajv.compileParser({type: "int32"}) assert.strictEqual(parseInt32("-2147483648"), -2147483648) assert.strictEqual(parseInt32("-2147483649"), -2147483649) assert.strictEqual(parseInt32("" + Number.MIN_SAFE_INTEGER), Number.MIN_SAFE_INTEGER) assert.strictEqual(parseInt32("2147483647"), 2147483647) assert.strictEqual(parseInt32("2147483648"), 2147483648) assert.strictEqual(parseInt32("" + Number.MAX_SAFE_INTEGER), Number.MAX_SAFE_INTEGER) const parseUint32 = ajv.compileParser({type: "uint32"}) assert.strictEqual(parseUint32("0"), 0) assert.strictEqual(parseUint32("-1"), undefined) assert.strictEqual(parseUint32("4294967295"), 4294967295) assert.strictEqual(parseUint32("4294967296"), 4294967296) assert.strictEqual(parseUint32("" + Number.MAX_SAFE_INTEGER), Number.MAX_SAFE_INTEGER) }) }) }) ================================================ FILE: spec/options/meta_validateSchema.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("meta and validateSchema options", () => { it("should add draft-7 meta schema by default", () => { testOptionMeta(new _Ajv()) testOptionMeta(new _Ajv({meta: true})) function testOptionMeta(ajv) { ajv.getSchema("http://json-schema.org/draft-07/schema").should.be.a("function") ajv.validateSchema({$id: "ok", type: "integer"}).should.equal(true) ajv.validateSchema({$id: "wrong", type: 123}).should.equal(false) should.not.throw(() => { ajv.addSchema({$id: "ok", type: "integer"}) }) should.throw(() => { ajv.addSchema({$id: "wrong", type: 123}) }, /schema is invalid/) } }) it("should throw if meta: false and validateSchema: true", () => { const ajv = new _Ajv({meta: false, logger: false}) should.not.exist(ajv.getSchema("http://json-schema.org/draft-07/schema")) should.not.throw(() => { ajv.addSchema({type: "wrong_type"}, "integer") }) }) it("should skip schema validation with validateSchema: false", () => { let ajv = new _Ajv() should.throw(() => { ajv.addSchema({type: 123}, "integer") }, /schema is invalid/) ajv = new _Ajv({validateSchema: false}) should.not.throw(() => { ajv.addSchema({type: 123}, "integer") }) ajv = new _Ajv({validateSchema: false, meta: false}) should.not.throw(() => { ajv.addSchema({type: 123}, "integer") }) }) describe('validateSchema: "log"', () => { let loggedError, loggedWarning const logger = { log() {}, warn: () => (loggedWarning = true), error: () => (loggedError = true), } beforeEach(() => { loggedError = false loggedWarning = false }) it("should not throw on invalid schema", () => { const ajv = new _Ajv({validateSchema: "log", logger}) should.not.throw(() => { ajv.addSchema({type: 123}, "integer") }) loggedError.should.equal(true) loggedWarning.should.equal(false) }) it("should not throw on invalid schema with meta: false", () => { const ajv = new _Ajv({validateSchema: "log", meta: false, logger}) should.not.throw(() => { ajv.addSchema({type: 123}, "integer") }) loggedError.should.equal(false) loggedWarning.should.equal(true) }) }) it("should validate v6 schema", () => { const ajv = new _Ajv() ajv.validateSchema({contains: {minimum: 2}}).should.equal(true) ajv.validateSchema({contains: 2}).should.equal(false) }) it("should use option meta as default meta schema", () => { const meta = { $schema: "http://json-schema.org/draft-07/schema", properties: { myKeyword: {type: "boolean"}, }, } let ajv = new _Ajv({meta: meta}) ajv.validateSchema({myKeyword: true}).should.equal(true) ajv.validateSchema({myKeyword: 2}).should.equal(false) ajv .validateSchema({ $schema: "http://json-schema.org/draft-07/schema", myKeyword: 2, }) .should.equal(true) ajv = new _Ajv() ajv.validateSchema({myKeyword: true}).should.equal(true) ajv.validateSchema({myKeyword: 2}).should.equal(true) }) }) ================================================ FILE: spec/options/nullable.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("nullable keyword", () => { let ajv beforeEach(() => { ajv = new _Ajv() }) it('should support keyword "nullable"', () => { testNullable({ type: "number", nullable: true, }) testNullable({ type: ["number"], nullable: true, }) testNullable({ type: ["number", "null"], }) testNullable({ type: ["number", "null"], nullable: true, }) testNotNullable({type: "number"}) testNotNullable({type: ["number"]}) }) it('should respect "nullable" == false', () => { testNotNullable({ type: "number", nullable: false, }) testNotNullable({ type: ["number"], nullable: false, }) }) it("should throw if type includes null with nullable: false", () => { should.throw(() => { ajv.compile({ type: ["number", "null"], nullable: false, }) }, "type: null contradicts nullable: false") }) it("should throw if nullable is used without type", () => { should.throw(() => { ajv.compile({ nullable: true, }) }, '"nullable" cannot be used without "type"') }) function testNullable(schema) { const validate = ajv.compile(schema) validate(1).should.equal(true) validate(null).should.equal(true) validate("1").should.equal(false) } function testNotNullable(schema) { const validate = ajv.compile(schema) validate(1).should.equal(true) validate(null).should.equal(false) validate("1").should.equal(false) } }) ================================================ FILE: spec/options/options_add_schemas.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("options to add schemas", () => { describe("schemas", () => { it("should add schemas from object", () => { const ajv = new _Ajv({ schemas: { int: {type: "integer"}, str: {type: "string"}, }, }) ajv.validate("int", 123).should.equal(true) ajv.validate("int", "foo").should.equal(false) ajv.validate("str", "foo").should.equal(true) ajv.validate("str", 123).should.equal(false) }) it("should add schemas from array", () => { const ajv = new _Ajv({ schemas: [ {$id: "int", type: "integer"}, {$id: "str", type: "string"}, { $id: "obj", type: "object", properties: { int: {$ref: "int"}, str: {$ref: "str"}, }, }, ], }) ajv.validate("obj", {int: 123, str: "foo"}).should.equal(true) ajv.validate("obj", {int: "foo", str: "bar"}).should.equal(false) ajv.validate("obj", {int: 123, str: 456}).should.equal(false) }) }) describe("addUsedSchema", () => { ;[true, undefined].forEach((optionValue) => { describe("= " + optionValue, () => { let ajv beforeEach(() => { ajv = new _Ajv({addUsedSchema: optionValue}) }) describe("compile and validate", () => { it("should add schema", () => { let schema = {$id: "str", type: "string"} const validate = ajv.compile(schema) validate("abc").should.equal(true) validate(1).should.equal(false) ajv.getSchema("str").should.equal(validate) schema = {$id: "int", type: "integer"} ajv.validate(schema, 1).should.equal(true) ajv.validate(schema, "abc").should.equal(false) ajv.getSchema("int").should.be.a("function") }) it("should throw with duplicate ID", () => { ajv.compile({$id: "str", type: "string"}) should.throw(() => { ajv.compile({$id: "str", type: "string", minLength: 2}) }, /already exists/) const schema = {$id: "int", type: "integer"} const schema2 = {$id: "int", type: "integer", minimum: 0} ajv.validate(schema, 1).should.equal(true) should.throw(() => { ajv.validate(schema2, 1) }, /already exists/) }) }) }) }) describe("= false", () => { let ajv beforeEach(() => { ajv = new _Ajv({addUsedSchema: false}) }) describe("compile and validate", () => { it("should NOT add schema", () => { let schema = {$id: "str", type: "string"} const validate = ajv.compile(schema) validate("abc").should.equal(true) validate(1).should.equal(false) should.equal(ajv.getSchema("str"), undefined) schema = {$id: "int", type: "integer"} ajv.validate(schema, 1).should.equal(true) ajv.validate(schema, "abc").should.equal(false) should.equal(ajv.getSchema("int"), undefined) }) it("should NOT throw with duplicate ID", () => { ajv.compile({$id: "str", type: "string"}) should.not.throw(() => { ajv.compile({$id: "str", type: "string", minLength: 2}) }) const schema = {$id: "int", type: "integer"} const schema2 = {$id: "int", type: "integer", minimum: 0} ajv.validate(schema, 1).should.equal(true) should.not.throw(() => { ajv.validate(schema2, 1).should.equal(true) }) }) }) }) }) }) ================================================ FILE: spec/options/options_code.spec.ts ================================================ import type Ajv from "../.." import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("code generation options", () => { describe("sourceCode", () => { describe("= true", () => { it("should add source.code property", () => { test(new _Ajv({code: {source: true}})) function test(ajv) { const validate = ajv.compile({type: "number"}) validate.source.validateCode.should.be.a("string") } }) }) describe("= false and default", () => { it("should not add source property", () => { test(new _Ajv()) test(new _Ajv({code: {source: false}})) function test(ajv: Ajv) { const validate = ajv.compile({type: "number"}) should.not.exist(validate.source) } }) }) }) describe("processCode", () => { it("should process generated code", () => { const ajv = new _Ajv() let validate = ajv.compile({type: "string"}) // TODO re-enable this test when option to strip whitespace is added // validate.toString().split("\n").length.should.equal(1) const unprocessedLines = validate.toString().split("\n").length const beautify = require("js-beautify").js_beautify const ajvPC = new _Ajv({code: {process: beautify}}) validate = ajvPC.compile({type: "string"}) validate.toString().split("\n").length.should.be.above(unprocessedLines) validate("foo").should.equal(true) validate(1).should.equal(false) }) }) describe("passContext option", () => { let ajv: Ajv, contexts: any[] beforeEach(() => { contexts = [] }) describe("= true", () => { it("should pass this value as context to user-defined keyword validation function", () => { const validate = getValidate(true) const self = {} validate.call(self, {}) contexts.should.have.length(4) contexts.forEach((ctx) => ctx.should.equal(self)) }) }) describe("= false", () => { it("should pass ajv instance as context to user-defined keyword validation function", () => { const validate = getValidate(false) const self = {} validate.call(self, {}) contexts.should.have.length(4) contexts.forEach((ctx) => ctx.should.equal(ajv)) }) }) function getValidate(passContext) { ajv = new _Ajv({passContext: passContext, inlineRefs: false}) ajv.addKeyword({keyword: "testValidate", validate: storeContext}) ajv.addKeyword({keyword: "testCompile", compile: compileTestValidate}) const schema = { definitions: { test1: { testValidate: true, testCompile: true, }, test2: { allOf: [{$ref: "#/definitions/test1"}], }, }, allOf: [{$ref: "#/definitions/test1"}, {$ref: "#/definitions/test2"}], } return ajv.compile(schema) } function storeContext(this: any) { contexts.push(this) return true } function compileTestValidate() { return storeContext } }) describe("loopEnum option", () => { it("should use loop if more values than specified", () => { const ajv1 = new _Ajv() const ajv2 = new _Ajv({loopEnum: 2}) test(ajv1, {enum: ["foo", "bar"]}) test(ajv2, {enum: ["foo", "bar"]}) test(ajv1, {enum: ["foo", "bar", "baz"]}) test(ajv2, {enum: ["foo", "bar", "baz"]}) function test(ajv, schema) { ajv.validate(schema, "foo").should.equal(true) ajv.validate(schema, "boo").should.equal(false) ajv.validate(schema, 1).should.equal(false) } }) }) }) ================================================ FILE: spec/options/options_refs.spec.ts ================================================ import _Ajv from "../ajv" import type {Options} from "../.." import chai from "../chai" const should = chai.should() describe("referenced schema options", () => { describe("ignoreKeywordsWithRef", () => { describe("= undefined", () => { it("should allow extending $ref with other keywords", () => { test({}, true) }) it("should NOT log warning", () => { testWarning() }) }) describe("= true", () => { it("should ignore other keywords when $ref is used", () => { test({ignoreKeywordsWithRef: true, logger: false}, false) }) it("should log warning when other keywords are used with $ref", () => { testWarning({ignoreKeywordsWithRef: true}, /keywords\signored/) }) }) function test(opts: Options, shouldExtendRef: boolean) { const ajv = new _Ajv(opts) const schema = { definitions: { int: {type: "integer"}, }, type: "number", $ref: "#/definitions/int", minimum: 10, } let validate = ajv.compile(schema) validate(10).should.equal(true) validate(1).should.equal(!shouldExtendRef) const schema1 = { definitions: { int: {type: "integer"}, }, type: "object", properties: { foo: { $ref: "#/definitions/int", type: "number", minimum: 10, }, bar: { type: "number", allOf: [{$ref: "#/definitions/int"}, {minimum: 10}], }, }, } validate = ajv.compile(schema1) validate({foo: 10, bar: 10}).should.equal(true) validate({foo: 1, bar: 10}).should.equal(!shouldExtendRef) validate({foo: 10, bar: 1}).should.equal(false) } function testWarning(opts: Options = {}, msgPattern?: RegExp) { let oldConsole try { oldConsole = console.warn let consoleMsg console.warn = function (...args: any[]) { consoleMsg = Array.prototype.join.call(args, " ") } const ajv = new _Ajv(opts) const schema = { definitions: { int: {type: "integer"}, }, type: "number", $ref: "#/definitions/int", minimum: 10, } ajv.compile(schema) if (msgPattern) consoleMsg.should.match(msgPattern) else should.not.exist(consoleMsg) } finally { console.warn = oldConsole } } }) describe("missingRefs", () => { it("should throw if ref is missing without this option", () => { const ajv = new _Ajv() should.throw(() => { ajv.compile({$ref: "missing_reference"}) }, /can't resolve reference missing_reference/) }) }) }) ================================================ FILE: spec/options/options_reporting.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("reporting options", () => { describe("verbose", () => { it("should add schema, parentSchema and data to errors with verbose option == true", () => { testVerbose(new _Ajv({verbose: true})) testVerbose(new _Ajv({verbose: true, allErrors: true})) function testVerbose(ajv) { const schema = { type: "object", properties: { foo: {type: "number", minimum: 5}, }, } const validate = ajv.compile(schema) const data = {foo: 3} validate(data).should.equal(false) validate.errors.should.have.length(1) const err = validate.errors[0] should.equal(err.schema, 5) err.parentSchema.should.eql({type: "number", minimum: 5}) err.parentSchema.should.equal(schema.properties.foo) // by reference should.equal(err.data, 3) } }) }) describe("allErrors", () => { it('should be disabled inside "not" keyword', () => { test(new _Ajv(), false) test(new _Ajv({allErrors: true}), true) function test(ajv, allErrors) { let format1called = false, format2called = false ajv.addFormat("format1", () => { format1called = true return false }) ajv.addFormat("format2", () => { format2called = true return false }) const schema1 = { type: "string", allOf: [{format: "format1"}, {format: "format2"}], } ajv.validate(schema1, "abc").should.equal(false) ajv.errors.should.have.length(allErrors ? 2 : 1) format1called.should.equal(true) format2called.should.equal(allErrors) const schema2 = { not: schema1, } format1called = format2called = false ajv.validate(schema2, "abc").should.equal(true) should.equal(ajv.errors, null) format1called.should.equal(true) format2called.should.equal(false) } }) }) describe("logger", () => { /** * The logger option tests are based on the meta scenario which writes into the logger.warn */ const origConsoleWarn = console.warn let consoleCalled beforeEach(() => { consoleCalled = false console.warn = () => (consoleCalled = true) }) afterEach(() => { console.warn = origConsoleWarn }) it("no user-defined logger is given - global console should be used", () => { const ajv = new _Ajv({meta: false}) ajv.compile({ type: "number", minimum: 1, }) should.equal(consoleCalled, true) }) it("user-defined logger is an object - logs should only report to it", () => { let loggerCalled = false const logger = { warn: log, log: log, error: log, } const ajv = new _Ajv({ meta: false, logger: logger, }) ajv.compile({ type: "number", minimum: 1, }) should.equal(loggerCalled, true) should.equal(consoleCalled, false) function log() { loggerCalled = true } }) it("logger option is false - no logs should be reported", () => { const ajv = new _Ajv({ meta: false, logger: false, }) ajv.compile({ type: "number", minimum: 1, }) should.equal(consoleCalled, false) }) it("logger option is an object without required methods - an error should be thrown", () => { const opts: any = { meta: false, logger: {}, } ;(() => new _Ajv(opts)).should.throw( Error, /logger must implement log, warn and error methods/ ) }) }) }) ================================================ FILE: spec/options/options_validation.spec.ts ================================================ import type Ajv from "../.." import _Ajv from "../ajv" import chai from "../chai" chai.should() const DATE_FORMAT = /^\d\d\d\d-[0-1]\d-[0-3]\d$/ describe("validation options", () => { describe("format", () => { it("should not validate formats if option format == false", () => { const ajv = new _Ajv({formats: {date: DATE_FORMAT}}), ajvFF = new _Ajv({formats: {date: DATE_FORMAT}, validateFormats: false}) const schema = {type: "string", format: "date"} const invalideDateTime = "06/19/1963" // expects hyphens ajv.validate(schema, invalideDateTime).should.equal(false) ajvFF.validate(schema, invalideDateTime).should.equal(true) }) }) describe("formats", () => { it("should add formats from options", () => { const ajv = new _Ajv({ allowUnionTypes: true, formats: { identifier: /^[a-z_$][a-z0-9_$]*$/i, }, }) const validate = ajv.compile({ type: ["string", "number"], format: "identifier", }) validate("Abc1").should.equal(true) validate("foo bar").should.equal(false) validate("123").should.equal(false) validate(123).should.equal(true) }) }) describe("keywords", () => { it("should add keywords from options", () => { const ajv = new _Ajv({ allowUnionTypes: true, keywords: [ { keyword: "identifier", type: "string", validate: function (_schema, data) { return /^[a-z_$][a-z0-9_$]*$/i.test(data) }, }, ], }) testKeyword(ajv) }) it("should support old keywords option as map", () => { const ajv = new _Ajv({ allowUnionTypes: true, keywords: { //@ts-expect-error identifier: { type: "string", schema: false, validate: function (data: string) { return /^[a-z_$][a-z0-9_$]*$/i.test(data) }, }, }, logger: false, }) testKeyword(ajv) }) function testKeyword(ajv: Ajv) { const validate = ajv.compile({ type: ["string", "number"], identifier: true, }) validate("Abc1").should.equal(true) validate("foo bar").should.equal(false) validate("123").should.equal(false) validate(123).should.equal(true) } }) describe("unicode", () => { it("should use String.prototype.length with deprecated unicode option == false", () => { const ajvUnicode = new _Ajv() testUnicode(new _Ajv({unicode: false, logger: false})) testUnicode(new _Ajv({unicode: false, allErrors: true, logger: false})) function testUnicode(ajv: Ajv) { let validateWithUnicode = ajvUnicode.compile({type: "string", minLength: 2}) let validate = ajv.compile({type: "string", minLength: 2}) validateWithUnicode("😀").should.equal(false) validate("😀").should.equal(true) validateWithUnicode = ajvUnicode.compile({type: "string", maxLength: 1}) validate = ajv.compile({type: "string", maxLength: 1}) validateWithUnicode("😀").should.equal(true) validate("😀").should.equal(false) } }) }) describe("multipleOfPrecision", () => { it("should allow for some deviation from 0 when validating multipleOf with value < 1", () => { test(new _Ajv({multipleOfPrecision: 7})) test(new _Ajv({multipleOfPrecision: 7, allErrors: true})) function test(ajv) { let schema = {type: "number", multipleOf: 0.01} let validate = ajv.compile(schema) validate(4.18).should.equal(true) validate(4.181).should.equal(false) schema = {type: "number", multipleOf: 0.0000001} validate = ajv.compile(schema) validate(53.198098).should.equal(true) validate(53.1980981).should.equal(true) validate(53.19809811).should.equal(false) } }) }) }) ================================================ FILE: spec/options/ownProperties.spec.ts ================================================ import _Ajv from "../ajv" import type Ajv from "../.." import chai from "../chai" chai.should() describe("ownProperties option", () => { let ajv: Ajv, ajvOP: Ajv, ajvOP1: Ajv beforeEach(() => { ajv = new _Ajv({allErrors: true}) ajvOP = new _Ajv({ownProperties: true, allErrors: true}) ajvOP1 = new _Ajv({ownProperties: true}) }) it("should only validate own properties with additionalProperties", () => { const schema = { type: "object", properties: {a: {type: "number"}}, additionalProperties: false, } const obj = {a: 1} const proto = {b: 2} test(schema, obj, proto) }) it("should only validate own properties with properties keyword", () => { const schema = { type: "object", properties: { a: {type: "number"}, b: {type: "number"}, }, } const obj = {a: 1} const proto = {b: "not a number"} test(schema, obj, proto) }) it("should only validate own properties with required keyword", () => { const schema = { type: "object", required: ["a", "b"], } const obj = {a: 1} const proto = {b: 2} test(schema, obj, proto, 1, true) }) it("should only validate own properties with required keyword - many properties", () => { ajv = new _Ajv({allErrors: true, loopRequired: 1}) ajvOP = new _Ajv({ownProperties: true, allErrors: true, loopRequired: 1}) ajvOP1 = new _Ajv({ownProperties: true, loopRequired: 1}) const schema = { type: "object", required: ["a", "b", "c", "d"], } const obj = {a: 1, b: 2} const proto = {c: 3, d: 4} test(schema, obj, proto, 2, true) }) it("should only validate own properties with required keyword as $data", () => { ajv = new _Ajv({allErrors: true, $data: true}) ajvOP = new _Ajv({ownProperties: true, allErrors: true, $data: true}) ajvOP1 = new _Ajv({ownProperties: true, $data: true}) const schema = { type: "object", required: {$data: "0/req"}, properties: { req: { type: "array", items: {type: "string"}, }, }, } const obj = { req: ["a", "b"], a: 1, } const proto = {b: 2} test(schema, obj, proto, 1, true) }) it("should only validate own properties with properties and required keyword", () => { const schema = { type: "object", properties: { a: {type: "number"}, b: {type: "number"}, }, required: ["a", "b"], } const obj = {a: 1} const proto = {b: 2} test(schema, obj, proto, 1, true) }) it("should only validate own properties with dependencies keyword", () => { const schema = { type: "object", dependencies: { a: ["c"], b: ["d"], }, } const obj = {a: 1, c: 3} const proto = {b: 2} test(schema, obj, proto) const obj1 = {a: 1, b: 2, c: 3} const proto1 = {d: 4} test(schema, obj1, proto1, 1, true) }) it("should only validate own properties with schema dependencies", () => { const schema = { type: "object", dependencies: { a: {not: {required: ["c"]}}, b: {not: {required: ["d"]}}, }, } const obj = {a: 1, d: 3} const proto = {b: 2} test(schema, obj, proto) const obj1 = {a: 1, b: 2} const proto1 = {d: 4} test(schema, obj1, proto1) }) it("should only validate own properties with patternProperties", () => { const schema = { type: "object", patternProperties: {"f.*o": {type: "integer"}}, } const obj = {fooo: 1} const proto = {foo: "not a number"} test(schema, obj, proto) }) it("should only validate own properties with propertyNames", () => { const schema = { type: "object", propertyNames: { pattern: "foo", }, } const obj = {foo: 2} const proto = {bar: 1} test(schema, obj, proto, 2) }) function test(schema, obj, proto, errors = 1, reverse?: boolean) { const validate = ajv.compile(schema) const validateOP = ajvOP.compile(schema) const validateOP1 = ajvOP1.compile(schema) const data = Object.create(proto) for (const key in obj) data[key] = obj[key] if (reverse) { validate(data).should.equal(true) validateOP(data).should.equal(false) validateOP.errors?.should.have.length(errors) validateOP1(data).should.equal(false) validateOP1.errors?.should.have.length(1) } else { validate(data).should.equal(false) validate.errors?.should.have.length(errors) validateOP(data).should.equal(true) validateOP1(data).should.equal(true) } } }) ================================================ FILE: spec/options/removeAdditional.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" chai.should() describe("removeAdditional option", () => { it("should remove all additional properties", () => { const ajv = new _Ajv({removeAdditional: "all"}) ajv.addSchema({ $id: "//test/fooBar", type: "object", properties: {foo: {type: "string"}, bar: {type: "string"}}, }) const object = { foo: "foo", bar: "bar", baz: "baz-to-be-removed", } ajv.validate("//test/fooBar", object).should.equal(true) object.should.have.property("foo") object.should.have.property("bar") object.should.not.have.property("baz") }) it("should remove properties that would error when `additionalProperties = false`", () => { const ajv = new _Ajv({removeAdditional: true}) ajv.addSchema({ $id: "//test/fooBar", type: "object", properties: {foo: {type: "string"}, bar: {type: "string"}}, additionalProperties: false, }) const object = { foo: "foo", bar: "bar", baz: "baz-to-be-removed", } ajv.validate("//test/fooBar", object).should.equal(true) object.should.have.property("foo") object.should.have.property("bar") object.should.not.have.property("baz") }) it("should remove properties that would error when `additionalProperties = false` (many properties, boolean schema)", () => { const ajv = new _Ajv({removeAdditional: true}) const schema = { type: "object", properties: { obj: { type: "object", additionalProperties: false, properties: { a: {type: "string"}, b: false, c: {type: "string"}, d: {type: "string"}, e: {type: "string"}, f: {type: "string"}, g: {type: "string"}, h: {type: "string"}, i: {type: "string"}, }, }, }, } const data = { obj: { a: "valid", b: "should not be removed", additional: "will be removed", }, } ajv.validate(schema, data).should.equal(false) data.should.eql({ obj: { a: "valid", b: "should not be removed", }, }) }) it("should remove properties that would error when `additionalProperties` is a schema", () => { const ajv = new _Ajv({removeAdditional: "failing"}) ajv.addSchema({ $id: "//test/fooBar", type: "object", properties: {foo: {type: "string"}, bar: {type: "string"}}, additionalProperties: {type: "string"}, }) const object = { foo: "foo", bar: "bar", baz: "baz-to-be-kept", fizz: 1000, } ajv.validate("//test/fooBar", object).should.equal(true) object.should.have.property("foo") object.should.have.property("bar") object.should.have.property("baz") object.should.not.have.property("fizz") ajv.addSchema({ $id: "//test/fooBar2", type: "object", properties: {foo: {type: "string"}, bar: {type: "string"}}, additionalProperties: {type: "string", pattern: "^to-be-", maxLength: 10}, }) const object1 = { foo: "foo", bar: "bar", baz: "to-be-kept", quux: "to-be-removed", fizz: 1000, } ajv.validate("//test/fooBar2", object1).should.equal(true) object1.should.have.property("foo") object1.should.have.property("bar") object1.should.have.property("baz") object1.should.not.have.property("fizz") }) }) ================================================ FILE: spec/options/schemaId.spec.ts ================================================ import type Ajv from "../.." import _Ajv from "../ajv" import assert = require("assert") import chai from "../chai" const should = chai.should() describe("removed schemaId option", () => { it("should use $id and throw exception when id is used", () => { test(new _Ajv({logger: false})) function test(ajv) { ajv.addSchema({$id: "mySchema1", type: "string"}) const validate = ajv.getSchema("mySchema1") validate("foo").should.equal(true) validate(1).should.equal(false) should.throw( () => ajv.compile({id: "mySchema2", type: "string"}), /NOT SUPPORTED: keyword "id"/ ) } }) it("should use $id and throw exception for id when strict: false", () => { test(new _Ajv({logger: false, strict: false})) function test(ajv: Ajv) { ajv.addSchema({$id: "mySchema1", type: "string"}) const validate = ajv.getSchema("mySchema1") assert(typeof validate == "function") validate("foo").should.equal(true) validate(1).should.equal(false) should.throw( () => ajv.compile({id: "mySchema2", type: "string"}), /NOT SUPPORTED: keyword "id"/ ) should.not.exist(ajv.getSchema("mySchema2")) } }) }) ================================================ FILE: spec/options/strict.spec.ts ================================================ import type {JSONSchemaType} from "../.." import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("strict mode", () => { describe( '"additionalItems" without "items"', testStrictMode({type: "array", additionalItems: false}, /additionalItems/) ) describe('"if" without "then" and "else"', testStrictMode({if: true}, /if.*then.*else/)) describe('"then" without "if"', testStrictMode({then: true}, /then.*if/)) describe('"else" without "if"', testStrictMode({else: true}, /else.*if/)) describe( '"properties" matching "patternProperties"', testStrictMode( { type: "object", properties: {foo: false}, patternProperties: {foo: false}, }, /property.*pattern/ ) ) describe('option allowMatchingProperties to allow "properties" matching "patternProperties"', () => { it("should NOT throw an error or log a warning", () => { const output: any = {} const ajv = new _Ajv({ allowMatchingProperties: true, logger: getLogger(output), }) const schema = { type: "object", properties: {foo: false}, patternProperties: {foo: false}, } ajv.compile(schema) should.not.exist(output.warning) }) }) describe("strictTypes option", () => { const ajv = new _Ajv({strictTypes: true}) const ajvUT = new _Ajv({strictTypes: true, allowUnionTypes: true}) describe("multiple/union types", () => { it("should prohibit multiple types", () => { should.throw(() => { ajv.compile({type: ["number", "string"]}) }, /use allowUnionTypes to allow union type/) }) it("should allow multiple types with option allowUnionTypes", () => { should.not.throw(() => { ajvUT.compile({type: ["number", "string"]}) }) }) it("should allow nullable", () => { should.not.throw(() => { ajv.compile({type: ["number", "null"]}) ajv.compile({type: ["number"], nullable: true}) }) }) }) describe("contradictory types", () => { it("should prohibit contradictory types", () => { should.throw(() => { ajv.compile({ type: "object", anyOf: [{type: "object"}, {type: "array"}], }) }, /type "array" not allowed by context "object"/) }) it("should allow narrowing types", () => { should.not.throw(() => { ajvUT.compile({ type: ["object", "array"], anyOf: [{type: "object"}, {type: "array"}], }) }) }) it('should allow "integer" in "number" context', () => { should.not.throw(() => { ajv.compile({ type: "number", anyOf: [{type: "integer"}], }) }) }) it('should prohibit "number" in "integer" context', () => { should.throw(() => { ajv.compile({ type: "integer", anyOf: [{type: "number"}], }) }, /type "number" not allowed by context "integer"/) }) }) describe("applicable types", () => { it("should prohibit keywords without applicable types", () => { should.throw(() => { ajv.compile({ properties: { foo: {type: "number", minimum: 0}, }, }) }, /missing type "object" for keyword "properties"/) should.throw(() => { ajv.compile({ type: "object", properties: { foo: {minimum: 0}, }, }) }, /missing type "number" for keyword "minimum"/) }) it("should allow keywords with applicable types", () => { should.not.throw(() => { ajv.compile({ type: "object", properties: { foo: {type: "number", minimum: 0}, }, }) }) }) it("should allow keywords with applicable type in parent schema", () => { should.not.throw(() => { ajv.compile({ type: "object", anyOf: [ { properties: { foo: {type: "number"}, }, }, { properties: { bar: {type: "string"}, }, }, ], }) }) }) }) describe("propertyNames", () => { it('should set default data type "string"', () => { ajv.compile({ type: "object", propertyNames: {maxLength: 5}, }) ajv.compile({ type: "object", propertyNames: {type: "string", maxLength: 5}, }) should.throw(() => { ajv.compile({ type: "object", propertyNames: {type: "number"}, }) }, /type "number" not allowed by context/) }) }) }) describe("option strictTuples", () => { const ajv = new _Ajv({strictTuples: true}) type MyTuple = [string, number] it("should prohibit unconstrained tuples", () => { const schema1: JSONSchemaType = { type: "array", items: [{type: "string"}, {type: "number"}], minItems: 2, additionalItems: false, } should.not.throw(() => { ajv.compile(schema1) }) const schema2: JSONSchemaType = { type: "array", items: [{type: "string"}, {type: "number"}], minItems: 2, maxItems: 2, } should.not.throw(() => { ajv.compile(schema2) }) //@ts-expect-error const badSchema1: JSONSchemaType = { type: "object", properties: { test: { type: "array", items: [{type: "string"}, {type: "number"}], additionalItems: false, }, }, } should.throw(() => { ajv.compile(badSchema1) }, / minItems or maxItems\/additionalItems are not specified or different at path "#\/properties\/test"/) //@ts-expect-error const badSchema2: JSONSchemaType = { type: "object", properties: { test: { type: "array", items: [{type: "string"}, {type: "number"}], minItems: 2, }, }, } should.throw(() => { ajv.compile(badSchema2) }, / minItems or maxItems\/additionalItems are not specified or different at path "#\/properties\/test"/) //@ts-expect-error const badSchema3: JSONSchemaType = { type: "object", properties: { test: { type: "array", items: [{type: "string"}, {type: "number"}], minItems: 2, maxItems: 3, }, }, } should.throw(() => { ajv.compile(badSchema3) }, / minItems or maxItems\/additionalItems are not specified or different at path "#\/properties\/test"/) }) }) describe("strictRequired option", () => { const ajv = new _Ajv({strictRequired: true}) describe("base case", () => { const schema = { type: "object", properties: { notTest: { type: "string", }, }, required: ["test"], } it("should prohibit with strictRequired: true", () => { should.throw( () => ajv.compile(schema), 'strict mode: required property "test" is not defined at "#" (strictRequired)' ) }) it("should NOT prohibit when strictRequired is not set", () => { should.not.throw(() => new _Ajv().compile(schema)) }) }) it("should prohibit in second level of a schema", () => { should.throw(() => { ajv.compile({ type: "object", properties: { test: { type: "object", properties: {}, required: ["keyname"], }, }, }) }, 'strict mode: required property "keyname" is not defined at "#/properties/test" (strictRequired)') }) it.skip("should not throw with a same level if then", () => { should.not.throw(() => { ajv.compile({ type: "object", properties: {foo: {}}, if: {required: ["foo"]}, then: {properties: {bar: {type: "boolean"}}}, }) }) }) it("should throw if a required property exists in a parent object but not in the subschema that the require keyword references", () => { should.throw(() => { ajv.compile({ type: "object", properties: { foo: { type: "object", required: "foo", properties: { test: { type: "integer", }, }, }, }, }) }) }) it("should throw if property exists in parent but not in actual object required references", () => { should.throw(() => { ajv.compile({ type: "object", properties: { foo: { type: "object", required: "foo", properties: { test: { type: "number", }, }, }, }, }) }) }) it.skip("should not throw because all referenced properties are defined", () => { should.not.throw(() => { ajv.compile({ type: "object", properties: {foo: {}, bar: {}}, allOf: [ { allOf: [ { if: {required: ["foo"]}, then: {required: ["bar"]}, }, ], }, ], }) }) }) it("should throw because baz does not exist as a property", () => { should.throw(() => { ajv.compile({ type: "object", properties: {foo: {}, bar: {}}, allOf: [ { allOf: [ { if: {required: ["bar"]}, then: {required: ["baz"]}, }, ], }, ], }) }) }) }) }) function testStrictMode(schema, logPattern) { return () => { describe("strict = false", () => { it("should NOT throw an error or log a warning", () => { const output: any = {} const ajv = new _Ajv({ strict: false, logger: getLogger(output), }) ajv.compile(schema) should.not.exist(output.warning) }) }) describe("strict = true or undefined", () => { it("should throw an error", () => { test(new _Ajv({strict: true})) test(new _Ajv()) function test(ajv) { should.throw(() => { ajv.compile(schema) }, logPattern) } }) }) describe('strict = "log"', () => { it("should log a warning", () => { const output: any = {} const ajv = new _Ajv({ strict: "log", logger: getLogger(output), }) ajv.compile(schema) output.warning.should.match(logPattern) }) }) } } function getLogger(output) { return { log() { throw new Error("log should not be called") }, warn(msg) { output.warning = msg }, error() { throw new Error("error should not be called") }, } } ================================================ FILE: spec/options/strictDefaults.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("strict option with defaults (replaced strictDefaults)", () => { describe("useDefaults = true", () => { describe("strict = false", () => { it("should NOT throw an error or log a warning given an ignored default", () => { const output: any = {} const ajv = new _Ajv({ useDefaults: true, strict: false, logger: getLogger(output), }) const schema = { default: 5, properties: {}, } ajv.compile(schema) should.not.exist(output.warning) }) it("should NOT throw an error or log a warning given an ignored default #2", () => { const output: any = {} const ajv = new _Ajv({ useDefaults: true, strict: false, logger: getLogger(output), }) const schema = { oneOf: [ {enum: ["foo", "bar"]}, { properties: { foo: { default: true, }, }, }, ], } ajv.compile(schema) should.not.exist(output.warning) }) }) describe("strict = true", () => { it("should throw an error given an ignored default in the schema root when strict is true or undefined", () => { test(new _Ajv({useDefaults: true})) test(new _Ajv({useDefaults: true, strict: true})) function test(ajv) { const schema = { default: 5, type: "object", properties: {}, } should.throw(() => ajv.compile(schema), /default is ignored in the schema root/) } }) it("should throw an error given an ignored default in oneOf when strict is true or undefined", () => { test(new _Ajv({useDefaults: true})) test(new _Ajv({useDefaults: true, strict: true})) function test(ajv) { const schema = { oneOf: [ {enum: ["foo", "bar"]}, { type: "object", properties: { foo: { default: true, }, }, }, ], } should.throw(() => { ajv.compile(schema) }, /default is ignored/) } }) }) describe('strict = "log"', () => { it('should log a warning given an ignored default in the schema root when strict is "log"', () => { const output: any = {} const ajv = new _Ajv({ useDefaults: true, strict: "log", logger: getLogger(output), }) const schema = { type: "object", default: 5, properties: {}, } ajv.compile(schema) output.warning.should.match(/default is ignored in the schema root/) }) it('should log a warning given an ignored default in oneOf when strict is "log"', () => { const output: any = {} const ajv = new _Ajv({ useDefaults: true, strict: "log", logger: getLogger(output), }) const schema = { oneOf: [ {enum: ["foo", "bar"]}, { type: "object", properties: { foo: { default: true, }, }, }, ], } ajv.compile(schema) output.warning.should.match(/default is ignored for: data.foo/) }) }) }) describe("useDefaults = false or undefined", () => { it("should NOT throw an error given an ignored default in the schema root when useDefaults is false", () => { test(new _Ajv({useDefaults: false})) test(new _Ajv({useDefaults: false, strict: true})) test(new _Ajv()) test(new _Ajv({strict: true})) function test(ajv) { const schema = { type: "object", default: 5, properties: {}, } should.not.throw(() => { ajv.compile(schema) }) } }) it("should NOT throw an error given an ignored default in oneOf when useDefaults is false", () => { test(new _Ajv({useDefaults: false})) test(new _Ajv({useDefaults: false, strict: true})) test(new _Ajv()) test(new _Ajv({strict: true})) function test(ajv) { const schema = { oneOf: [ {enum: ["foo", "bar"]}, { type: "object", properties: { foo: { default: true, }, }, }, ], } should.not.throw(() => { ajv.compile(schema) }) } }) }) function getLogger(output) { return { log: () => { throw new Error("log should not be called") }, warn: function (warning) { output.warning = warning }, error: () => { throw new Error("error should not be called") }, } } }) ================================================ FILE: spec/options/strictKeywords.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("strict option with keywords (replaced strictKeywords)", () => { describe("strict = false", () => { it("should NOT throw an error or log a warning given an unknown keyword", () => { const output: any = {} const ajv = new _Ajv({ strict: false, logger: getLogger(output), }) const schema = { properties: {}, unknownKeyword: 1, } ajv.compile(schema) should.not.exist(output.warning) }) }) describe("strict = true or undefined", () => { it("should throw an error given an unknown keyword in the schema root when strict is true", () => { test(new _Ajv({strict: true})) test(new _Ajv()) function test(ajv) { const schema = { type: "object", properties: {}, unknownKeyword: 1, } should.throw(() => ajv.compile(schema), /unknown keyword/) } }) }) describe('strict = "log"', () => { it("should log an error given an unknown keyword in the schema root", () => { const output: any = {} const ajv = new _Ajv({ strict: "log", logger: getLogger(output), }) const schema = { type: "object", properties: {}, unknownKeyword: 1, } ajv.compile(schema) output.warning.should.match(/unknown keyword: "unknownKeyword"/) }) }) describe("unknown keyword inside schema that has no known keyword in compound keyword", () => { it("should throw an error given an unknown keyword when strict is true or undefined", () => { test(new _Ajv({strict: true})) test(new _Ajv()) function test(ajv) { const schema = { anyOf: [{unknownKeyword: 1}], } should.throw(() => ajv.compile(schema), /unknown keyword/) } }) }) function getLogger(output) { return { log() { throw new Error("log should not be called") }, warn(msg) { output.warning = msg }, error() { throw new Error("warn should not be called") }, } } }) ================================================ FILE: spec/options/strictNumbers.spec.ts ================================================ import _Ajv from "../ajv" describe("strict option with keywords (replaced structNumbers)", () => { describe("strict default", testStrict(new _Ajv())) describe("strict = true", testStrict(new _Ajv({strict: true}))) describe('strict = "log"', testStrict(new _Ajv({strict: "log"}))) describe("strict = false", testNotStrict(new _Ajv({strict: false}))) }) function testStrict(ajv) { return () => { it("should fail validation for NaN/Infinity as type number", () => { const validate = ajv.compile({type: "number"}) validate("1.1").should.equal(false) validate(1.1).should.equal(true) validate(1).should.equal(true) validate(NaN).should.equal(false) validate(Infinity).should.equal(false) }) it("should fail validation for NaN as type integer", () => { const validate = ajv.compile({type: "integer"}) validate("1.1").should.equal(false) validate(1.1).should.equal(false) validate(1).should.equal(true) validate(NaN).should.equal(false) validate(Infinity).should.equal(false) }) } } function testNotStrict(_ajv) { return () => { it("should NOT fail validation for NaN/Infinity as type number", () => { const validate = _ajv.compile({type: "number"}) validate("1.1").should.equal(false) validate(1.1).should.equal(true) validate(1).should.equal(true) validate(NaN).should.equal(true) validate(Infinity).should.equal(true) }) it("should NOT fail validation for NaN/Infinity as type integer", () => { const validate = _ajv.compile({type: "integer"}) validate("1.1").should.equal(false) validate(1.1).should.equal(false) validate(1).should.equal(true) validate(NaN).should.equal(false) validate(Infinity).should.equal(true) }) } } ================================================ FILE: spec/options/unicodeRegExp.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("unicodeRegExp option", () => { const unicodeChar = "\uD83D\uDC4D" const unicodeSchema = { type: "string", pattern: `^[${unicodeChar}]$`, } const schemaWithEscape = { type: "string", pattern: "^[\\:]$", } const patternPropertiesSchema = { type: "object", patternProperties: { "^\\:.*$": {type: "number"}, }, additionalProperties: false, } describe("= true (default)", () => { const ajv = new _Ajv() it("should fail schema compilation if used invalid (unnecessary) escape sequence for pattern", () => { should.throw(() => { ajv.compile(schemaWithEscape) }, /Invalid escape/) }) it("should fail schema compilation if used invalid (unnecessary) escape sequence for patternProperties", () => { should.throw(() => { ajv.compile(patternPropertiesSchema) }, /Invalid escape/) }) it("should validate unicode character", () => { const validate = ajv.compile(unicodeSchema) validate(unicodeChar).should.equal(true) }) }) describe("= false", () => { const ajv = new _Ajv({unicodeRegExp: false}) it("should pass schema compilation if used unnecessary escape sequence for pattern", () => { should.not.throw(() => { const validate = ajv.compile(schemaWithEscape) validate(":").should.equal(true) }) }) it("should pass schema compilation if used unnecessary escape sequence for patternProperties", () => { should.not.throw(() => { const validate = ajv.compile(patternPropertiesSchema) validate({":test": 1}).should.equal(true) validate({test: 1}).should.equal(false) }) }) it("should not validate unicode character", () => { const validate = ajv.compile(unicodeSchema) validate(unicodeChar).should.equal(false) }) }) }) ================================================ FILE: spec/options/unknownFormats.spec.ts ================================================ import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() const DATE_FORMAT = /^\d\d\d\d-[0-1]\d-[0-3]\d$/ describe("specifying allowed unknown formats with `formats` option", () => { describe("= true (default)", () => { it("should fail schema compilation if unknown format is used", () => { test(new _Ajv()) function test(ajv) { should.throw(() => { ajv.compile({type: "string", format: "unknown"}) }, /unknown format/) } }) it("should fail validation if unknown format is used via $data", () => { test(new _Ajv({$data: true})) function test(ajv) { ajv.addFormat("date", DATE_FORMAT) const validate = ajv.compile({ type: "object", properties: { foo: {type: "string", format: {$data: "1/bar"}}, bar: {type: "string"}, }, }) validate({foo: 1, bar: "unknown"}).should.equal(false) validate({foo: "2016-10-16", bar: "date"}).should.equal(true) validate({foo: "20161016", bar: "date"}).should.equal(false) validate({foo: "20161016"}).should.equal(true) validate({foo: "2016-10-16", bar: "unknown"}).should.equal(false) } }) }) describe('= "ignore (default before 5.0.0)"', () => { it("should pass schema compilation and be valid if unknown format is used", () => { test(new _Ajv({strict: false, logger: false})) function test(ajv) { const validate = ajv.compile({format: "unknown"}) validate("anything").should.equal(true) } }) it("should be valid if unknown format is used via $data", () => { test(new _Ajv({$data: true, strict: false})) function test(ajv) { ajv.addFormat("date", DATE_FORMAT) const validate = ajv.compile({ properties: { foo: {format: {$data: "1/bar"}}, bar: {type: "string"}, }, }) validate({foo: 1, bar: "unknown"}).should.equal(true) validate({foo: "2016-10-16", bar: "date"}).should.equal(true) validate({foo: "20161016", bar: "date"}).should.equal(false) validate({foo: "20161016"}).should.equal(true) validate({foo: "2016-10-16", bar: "unknown"}).should.equal(true) } }) }) describe("= [String]", () => { it("should pass schema compilation and be valid if allowed unknown format is used", () => { test(new _Ajv({formats: {allowed: true}})) function test(ajv) { const validate = ajv.compile({type: "string", format: "allowed"}) validate("anything").should.equal(true) should.throw(() => { ajv.compile({type: "string", format: "unknown"}) }, /unknown format/) } }) it("should be valid if allowed unknown format is used via $data", () => { test(new _Ajv({$data: true, formats: {allowed: true}, allowUnionTypes: true})) function test(ajv) { ajv.addFormat("date", DATE_FORMAT) const validate = ajv.compile({ type: "object", properties: { foo: {type: ["string", "number"], format: {$data: "1/bar"}}, bar: {type: "string"}, }, }) validate({foo: 1, bar: "allowed"}).should.equal(true) validate({foo: 1, bar: "unknown"}).should.equal(false) validate({foo: "2016-10-16", bar: "date"}).should.equal(true) validate({foo: "20161016", bar: "date"}).should.equal(false) validate({foo: "20161016"}).should.equal(true) validate({foo: "2016-10-16", bar: "allowed"}).should.equal(true) validate({foo: "2016-10-16", bar: "unknown"}).should.equal(false) } }) }) }) ================================================ FILE: spec/options/useDefaults.spec.ts ================================================ import _Ajv from "../ajv" import getAjvInstances from "../ajv_instances" import chai from "../chai" chai.should() describe("useDefaults option", () => { it("should replace undefined property with default value", () => { const instances = getAjvInstances( _Ajv, { allErrors: true, loopRequired: 3, }, {useDefaults: true} ) instances.forEach(test) function test(ajv) { const schema = { type: "object", properties: { foo: {type: "string", default: "abc"}, bar: {type: "number", default: 1}, baz: {type: "boolean", default: false}, nil: {type: "null", default: null}, obj: {type: "object", default: {}}, arr: {type: "array", default: []}, }, required: ["foo", "bar", "baz", "nil", "obj", "arr"], minProperties: 6, } const validate = ajv.compile(schema) let data = {} validate(data).should.equal(true) data.should.eql({ foo: "abc", bar: 1, baz: false, nil: null, obj: {}, arr: [], }) data = {foo: "foo", bar: 2, obj: {test: true}} validate(data).should.equal(true) data.should.eql({ foo: "foo", bar: 2, baz: false, nil: null, obj: {test: true}, arr: [], }) } }) it("should replace undefined item with default value", () => { test(new _Ajv({useDefaults: true})) test(new _Ajv({useDefaults: true, allErrors: true})) function test(ajv) { const schema = { type: "array", items: [ {type: "string", default: "abc"}, {type: "number", default: 1}, {type: "boolean", default: false}, ], minItems: 3, additionalItems: false, } const validate = ajv.compile(schema) let data: any = [] validate(data).should.equal(true) data.should.eql(["abc", 1, false]) data = ["foo"] validate(data).should.equal(true) data.should.eql(["foo", 1, false]) data = ["foo", 2, "false"] validate(data).should.equal(false) validate.errors.should.have.length(1) data.should.eql(["foo", 2, "false"]) } }) it('should apply default in "then" subschema (issue #635)', () => { test(new _Ajv({useDefaults: true})) test(new _Ajv({useDefaults: true, allErrors: true})) function test(ajv) { const schema = { type: "object", if: {required: ["foo"]}, then: { properties: { bar: {default: 2}, }, }, else: { properties: { foo: {default: 1}, }, }, } const validate = ajv.compile(schema) let data = {} validate(data).should.equal(true) data.should.eql({foo: 1}) data = {foo: 1} validate(data).should.equal(true) data.should.eql({foo: 1, bar: 2}) } }) describe("useDefaults: defaults are always passed by value", () => { it("should NOT modify underlying defaults when modifying validated data", () => { test(new _Ajv({useDefaults: true})) test(new _Ajv({useDefaults: true, allErrors: true})) }) function test(ajv) { const schema = { type: "object", properties: { items: { type: "array", default: ["a-default"], }, }, } const validate = ajv.compile(schema) const data: any = {} validate(data).should.equal(true) data.items.should.eql(["a-default"]) data.items.push("another-value") data.items.should.eql(["a-default", "another-value"]) const data2: any = {} validate(data2).should.equal(true) data2.items.should.eql(["a-default"]) } }) describe('defaults with "empty" values', () => { let schema, data beforeEach(() => { schema = { type: "object", properties: { obj: { type: "object", properties: { str: {default: "foo"}, n1: {default: 1}, n2: {default: 2}, n3: {default: 3}, }, }, arr: { type: "array", items: [{default: "foo"}, {default: 1}, {default: 2}, {default: 3}], minItems: 4, additionalItems: false, }, }, } data = { obj: { str: "", n1: null, n2: undefined, }, arr: ["", null, undefined], } }) it('should NOT assign defaults when useDefaults is true/"shared"', () => { test(new _Ajv({useDefaults: true})) function test(ajv) { const validate = ajv.compile(schema) validate(data).should.equal(true) data.should.eql({ obj: { str: "", n1: null, n2: 2, n3: 3, }, arr: ["", null, 2, 3], }) } }) it('should assign defaults when useDefaults = "empty"', () => { const ajv = new _Ajv({useDefaults: "empty"}) const validate = ajv.compile(schema) validate(data).should.equal(true) data.should.eql({ obj: { str: "foo", n1: 1, n2: 2, n3: 3, }, arr: ["foo", 1, 2, 3], }) }) }) }) ================================================ FILE: spec/remotes/bar.json ================================================ { "$id": "http://localhost:1234/bar.json", "type": "string" } ================================================ FILE: spec/remotes/buu.json ================================================ { "$id": "http://localhost:1234/buu.json", "definitions": { "buu": { "type": "object", "properties": { "bar": {"$ref": "bar.json"} } } } } ================================================ FILE: spec/remotes/first.json ================================================ { "$id": "http://localhost:1234/first.json", "type": "string" } ================================================ FILE: spec/remotes/foo.json ================================================ { "$id": "http://localhost:1234/foo.json", "type": "object", "properties": { "bar": {"$ref": "bar.json"} } } ================================================ FILE: spec/remotes/hyper-schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/hyper-schema#", "$id": "http://json-schema.org/draft-07/hyper-schema#", "title": "JSON Hyper-Schema", "definitions": { "schemaArray": { "allOf": [ { "$ref": "http://json-schema.org/draft-07/schema#/definitions/schemaArray" }, { "items": {"$ref": "#"} } ] } }, "allOf": [{"$ref": "http://json-schema.org/draft-07/schema#"}], "properties": { "additionalItems": {"$ref": "#"}, "additionalProperties": {"$ref": "#"}, "dependencies": { "additionalProperties": { "anyOf": [{"$ref": "#"}, {"type": "array"}] } }, "items": { "anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/schemaArray"}] }, "definitions": { "additionalProperties": {"$ref": "#"} }, "patternProperties": { "additionalProperties": {"$ref": "#"} }, "properties": { "additionalProperties": {"$ref": "#"} }, "if": {"$ref": "#"}, "then": {"$ref": "#"}, "else": {"$ref": "#"}, "allOf": {"$ref": "#/definitions/schemaArray"}, "anyOf": {"$ref": "#/definitions/schemaArray"}, "oneOf": {"$ref": "#/definitions/schemaArray"}, "not": {"$ref": "#"}, "contains": {"$ref": "#"}, "propertyNames": {"$ref": "#"}, "base": { "type": "string", "format": "uri-template" }, "links": { "type": "array", "items": { "$ref": "http://json-schema.org/draft-07/hyper-schema#/links" } } }, "links": [ { "rel": "self", "href": "{+%24id}" } ] } ================================================ FILE: spec/remotes/name.json ================================================ { "definitions": { "orNull": { "anyOf": [{"type": "null"}, {"$ref": "#"}] } }, "type": "string" } ================================================ FILE: spec/remotes/node.json ================================================ { "$id": "http://localhost:1234/node.json", "description": "node", "type": "object", "properties": { "value": {"type": "number"}, "subtree": {"$ref": "tree.json"} }, "required": ["value"] } ================================================ FILE: spec/remotes/scope_change.json ================================================ { "$id": "http://localhost:1234/scope_change.json", "definitions": { "foo": { "$id": "http://localhost:1234/scope_foo.json", "definitions": { "bar": { "type": "string" } } }, "baz": { "$id": "folder/", "type": "array", "items": {"$ref": "folderInteger.json"}, "bar": { "items": {"$ref": "folderInteger.json"} } } } } ================================================ FILE: spec/remotes/second.json ================================================ { "$id": "http://localhost:1234/second.json", "type": "object", "properties": { "first": {"$ref": "first.json"} } } ================================================ FILE: spec/remotes/tree.json ================================================ { "$id": "http://localhost:1234/tree.json", "description": "tree of nodes", "type": "object", "properties": { "meta": {"type": "string"}, "nodes": { "type": "array", "items": {"$ref": "node.json"} } }, "required": ["meta", "nodes"] } ================================================ FILE: spec/resolve.spec.ts ================================================ import type AjvCore from "../dist/core" import getAjvInstances from "./ajv_instances" import _Ajv from "./ajv" import type {AnyValidateFunction} from "../dist/types" import type MissingRefError from "../dist/compile/ref_error" import chai from "./chai" import * as uriJs from "uri-js" const should = chai.should() const uriResolvers = [undefined, uriJs] uriResolvers.forEach((resolver) => { let describeTitle: string if (resolver !== undefined) { describeTitle = "uri-js resolver" } else { describeTitle = "fast-uri resolver" } describe(describeTitle, () => { describe("resolve", () => { let instances: AjvCore[] beforeEach(() => { instances = getAjvInstances(_Ajv, { allErrors: true, verbose: true, inlineRefs: false, allowUnionTypes: true, uriResolver: resolver, }) }) describe("resolve.ids method", () => { it("should resolve ids in schema", () => { // Example from http://json-schema.org/latest/json-schema-core.html#anchor29 const schema = { $id: "http://x.y.z/rootschema.json#", $defs: { schema1: { $id: "#foo", description: "schema1", type: "integer", }, schema2: { $id: "otherschema.json", description: "schema2", $defs: { nested: { $id: "#bar", description: "nested", type: "string", }, alsonested: { $id: "t/inner.json#a", description: "alsonested", type: "boolean", }, }, }, schema3: { $id: "some://where.else/completely#", description: "schema3", type: "null", }, }, type: "object", properties: { foo: {$ref: "#foo"}, bar: {$ref: "otherschema.json#bar"}, baz: {$ref: "t/inner.json#a"}, bax: {$ref: "some://where.else/completely#"}, }, required: ["foo", "bar", "baz", "bax"], } instances.forEach((ajv) => { const validate = ajv.compile(schema) const data = {foo: 1, bar: "abc", baz: true, bax: null} validate(data).should.equal(true) }) }) it("should resolve fragment $id in schema refs when root $id not present", () => { const schema = { $schema: "http://json-schema.org/draft-07/schema#", definitions: { SeeAlso: {$id: "#SeeAlso", type: "number"}, Engine: { $id: "#Engine", type: "object", properties: { see_also: {$ref: "#SeeAlso"}, }, }, }, } instances.forEach((ajv) => { ajv.addSchema(schema, "yaml.json") const data = {see_also: 1} const validate = ajv.validate("yaml.json#/definitions/Engine", data) validate.should.equal(true) }) }) it("should throw if the same id resolves to two different schemas", () => { instances.forEach((ajv) => { ajv.compile({ $id: "http://example.com/1.json", type: "integer", }) should.throw(() => { ajv.compile({ type: "object", additionalProperties: { $id: "http://example.com/1.json", type: "string", }, }) }, /resolves to more than one schema/) should.throw(() => { ajv.compile({ type: ["object", "array"], items: { $id: "#int", type: "integer", }, additionalProperties: { $id: "#int", type: "string", }, }) }, /resolves to more than one schema/) }) }) it("should resolve ids defined as urn's (issue #423)", () => { const schema = { type: "object", properties: { ip1: { $id: "urn:some:ip:prop", type: "string", pattern: "^(\\d+\\.){3}\\d+$", }, ip2: { $ref: "urn:some:ip:prop", }, }, required: ["ip1", "ip2"], } const data = { ip1: "0.0.0.0", ip2: "0.0.0.0", } instances.forEach((ajv) => { const validate = ajv.compile(schema) validate(data).should.equal(true) }) }) }) describe("protocol-relative URIs", () => { it("should resolve fragment", () => { instances.forEach((ajv) => { const schema = { $id: "//e.com/types", definitions: { int: {type: "integer"}, }, } ajv.addSchema(schema) const validate = ajv.compile({$ref: "//e.com/types#/definitions/int"}) validate(1).should.equal(true) validate("foo").should.equal(false) }) }) }) describe("URIs with encoded characters (issue #2447)", () => { it("should resolve the ref", () => { const schema = { $ref: "#/definitions/Record%3Cstring%2CPerson%3E", $schema: "http://json-schema.org/draft-07/schema#", definitions: { Person: { type: "object", properties: { firstName: { type: "string", description: "The person's first name.", }, }, }, "Record": { type: "object", additionalProperties: { $ref: "#/definitions/Person", }, }, }, } const data = { joe: { firstName: "Joe", }, } instances.forEach((ajv) => { const validate = ajv.compile(schema) validate(data).should.equal(true) }) }) }) describe("missing schema error", function () { this.timeout(4000) it("should contain missingRef and missingSchema", () => { testMissingSchemaError({ baseId: "http://example.com/1.json", ref: "http://another.com/int.json", expectedMissingRef: "http://another.com/int.json", expectedMissingSchema: "http://another.com/int.json", }) }) it("should resolve missingRef and missingSchema relative to base id", () => { testMissingSchemaError({ baseId: "http://example.com/folder/1.json", ref: "int.json", expectedMissingRef: "http://example.com/folder/int.json", expectedMissingSchema: "http://example.com/folder/int.json", }) }) it("should resolve missingRef and missingSchema relative to base id from root", () => { testMissingSchemaError({ baseId: "http://example.com/folder/1.json", ref: "/int.json", expectedMissingRef: "http://example.com/int.json", expectedMissingSchema: "http://example.com/int.json", }) }) it("missingRef should and missingSchema should NOT include JSON path (hash fragment)", () => { testMissingSchemaError({ baseId: "http://example.com/1.json", ref: "int.json#/definitions/positive", expectedMissingRef: "http://example.com/int.json#/definitions/positive", expectedMissingSchema: "http://example.com/int.json", }) }) it("should throw missing schema error if same path exist in the current schema but id is different (issue #220)", () => { testMissingSchemaError({ baseId: "http://example.com/parent.json", ref: "object.json#/properties/a", expectedMissingRef: "http://example.com/object.json#/properties/a", expectedMissingSchema: "http://example.com/object.json", }) }) function testMissingSchemaError(opts) { instances.forEach((ajv) => { try { ajv.compile({ $id: opts.baseId, type: "object", properties: {a: {$ref: opts.ref}}, }) } catch (err) { const e = err as MissingRefError e.missingRef.should.equal(opts.expectedMissingRef) e.missingSchema.should.equal(opts.expectedMissingSchema) } }) } }) describe("inline referenced schemas without refs in them", () => { const schemas = [ {$id: "http://e.com/obj.json#", type: "object", properties: {a: {$ref: "int.json#"}}}, {$id: "http://e.com/int.json#", type: "integer", minimum: 2, maximum: 4}, { $id: "http://e.com/obj1.json#", type: "object", definitions: {int: {type: "integer", minimum: 2, maximum: 4}}, properties: {a: {$ref: "#/definitions/int"}}, }, {$id: "http://e.com/list.json#", type: "array", items: {$ref: "obj.json#"}}, ] it("by default should inline schema if it doesn't contain refs", () => { const ajv = new _Ajv({schemas, code: {source: true}}) testSchemas(ajv, true) }) it("should NOT inline schema if option inlineRefs == false", () => { const ajv = new _Ajv({schemas, inlineRefs: false, code: {source: true}}) testSchemas(ajv, false) }) it("should inline schema if option inlineRefs is bigger than number of keys in referenced schema", () => { const ajv = new _Ajv({schemas, inlineRefs: 4, code: {source: true}}) testSchemas(ajv, true) }) it("should NOT inline schema if option inlineRefs is less than number of keys in referenced schema", () => { const ajv = new _Ajv({schemas, inlineRefs: 2, code: {source: true}}) testSchemas(ajv, false) }) it("should avoid schema substitution when refs are inlined (issue #77)", () => { const ajv = new _Ajv({verbose: true}) const schemaMessage = { $schema: "http://json-schema.org/draft-07/schema#", $id: "http://e.com/message.json#", type: "object", required: ["header"], properties: { header: { type: "object", allOf: [{$ref: "header.json"}, {properties: {msgType: {enum: [0]}}}], }, }, } // header schema const schemaHeader = { $schema: "http://json-schema.org/draft-07/schema#", $id: "http://e.com/header.json#", type: "object", properties: { version: { type: "integer", maximum: 5, }, msgType: {type: "integer"}, }, required: ["version", "msgType"], } // a good message const validMessage = { header: { version: 4, msgType: 0, }, } // a bad message const invalidMessage = { header: { version: 6, msgType: 0, }, } // add schemas and get validator function ajv.addSchema(schemaHeader) ajv.addSchema(schemaMessage) const v: any = ajv.getSchema("http://e.com/message.json#") v(validMessage).should.equal(true) v.schema.$id.should.equal("http://e.com/message.json#") v(invalidMessage).should.equal(false) v.errors.should.have.length(1) v.schema.$id.should.equal("http://e.com/message.json#") v(validMessage).should.equal(true) v.schema.$id.should.equal("http://e.com/message.json#") }) function testSchemas(ajv, expectedInlined) { const v1 = ajv.getSchema("http://e.com/obj.json"), v2 = ajv.getSchema("http://e.com/obj1.json"), v3 = ajv.getSchema("http://e.com/list.json") testObjSchema(v1) testObjSchema(v2) testListSchema(v3) testInlined(v1, expectedInlined) testInlined(v2, expectedInlined) testInlined(v3, false) } function testObjSchema(validate) { validate({a: 3}).should.equal(true) validate({a: 1}).should.equal(false) validate({a: 5}).should.equal(false) } function testListSchema(validate) { validate([{a: 3}]).should.equal(true) validate([{a: 1}]).should.equal(false) validate([{a: 5}]).should.equal(false) } function testInlined(validate: AnyValidateFunction, expectedInlined) { const inlined: any = !validate.source?.scopeValues.validate inlined.should.equal(expectedInlined) } }) describe("duplicate internal $id", () => { it("should throw error with duplicate IDs in definitions", () => { const schema = { $id: "http://example.com/example.json", $defs: { foo: { $id: "#nope", type: "integer", }, bar: { $id: "#nope", type: "string", }, }, type: "object", properties: { foo: {$ref: "#/$defs/foo"}, bar: {$ref: "#/$defs/bar"}, }, } instances.forEach((ajv) => should.throw(() => ajv.compile(schema), /nope.*resolves to more than one schema/) ) }) it("should throw error with duplicate IDs in properties", () => { const schema = { $id: "http://example.com/example.json", type: "object", properties: { foo: { $id: "#nope", type: "integer", }, bar: { $id: "#nope", type: "string", }, }, } instances.forEach((ajv) => should.throw(() => ajv.compile(schema), /nope.*resolves to more than one schema/) ) }) }) }) }) }) ================================================ FILE: spec/schema-tests.spec.ts ================================================ import type AjvCore from "../dist/core" import _Ajv from "./ajv" import getAjvInstances from "./ajv_instances" import {withStandalone} from "./ajv_standalone" import jsonSchemaTest = require("json-schema-test") import options from "./ajv_options" import {afterError, afterEach} from "./after_test" import ajvFormats from "ajv-formats" const instances = getAjvInstances(_Ajv, options, {strict: false, formats: {allowedUnknown: true}}) const remoteRefs = { "http://localhost:1234/integer.json": require("./JSON-Schema-Test-Suite/remotes/integer.json"), "http://localhost:1234/folder/folderInteger.json": require("./JSON-Schema-Test-Suite/remotes/baseUriChange/folderInteger.json"), "http://localhost:1234/name.json": require("./remotes/name.json"), } const remoteRefsWithIds = [ require("./remotes/bar.json"), require("./remotes/foo.json"), require("./remotes/buu.json"), require("./remotes/tree.json"), require("./remotes/node.json"), require("./remotes/second.json"), require("./remotes/first.json"), require("./remotes/scope_change.json"), ] instances.forEach(addRemoteRefsAndFormats) jsonSchemaTest(withStandalone(instances), { description: `Schema tests of ${instances.length} ajv instances with different options`, suites: {"Schema tests": require("./_json/tests")}, only: [], assert: require("./chai").assert, afterError, afterEach, cwd: __dirname, timeout: 10000, }) function addRemoteRefsAndFormats(ajv: AjvCore) { ajv.opts.code.source = true for (const id in remoteRefs) ajv.addSchema(remoteRefs[id], id) ajv.addSchema(remoteRefsWithIds) ajvFormats(ajv) } ================================================ FILE: spec/security/array.json ================================================ [ { "description": "uniqueItems without type keyword should be used together with maxItems", "schema": { "$ref": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#" }, "tests": [ { "description": "uniqueItems keyword used without maxItems is invalid", "data": { "uniqueItems": true }, "valid": false }, { "description": "uniqueItems keyword used with maxItems is valid", "data": { "uniqueItems": true, "maxItems": "10" }, "valid": true }, { "description": "uniqueItems: false is ignored (and valid)", "data": { "uniqueItems": false }, "valid": true } ] }, { "description": "uniqueItems with scalar type(s) is valid to use without maxItems", "schema": { "$ref": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#" }, "tests": [ { "description": "uniqueItems keyword with a single scalar type is valid", "data": { "uniqueItems": true, "items": { "type": "number" } }, "valid": true }, { "description": "uniqueItems keyword with multiple scalar types is valid", "data": { "uniqueItems": true, "items": { "type": ["number", "string"] } }, "valid": true } ] }, { "description": "uniqueItems with compound type(s) should be used together with maxItems", "schema": { "$ref": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#" }, "tests": [ { "description": "uniqueItems keyword with a single compound type and without maxItems is invalid", "data": { "uniqueItems": true, "items": { "type": "object" } }, "valid": false }, { "description": "uniqueItems keyword with a single compound type and with maxItems is valid", "data": { "uniqueItems": true, "maxItems": "10", "items": { "type": "object" } }, "valid": true }, { "description": "uniqueItems keyword with multiple types including compound type and without maxItems is invalid", "data": { "uniqueItems": true, "items": { "type": ["array", "number"] } }, "valid": false }, { "description": "uniqueItems keyword with multiple types including compound type and with maxItems is valid", "data": { "uniqueItems": true, "maxItems": "10", "items": { "type": ["array", "number"] } }, "valid": true } ] } ] ================================================ FILE: spec/security/object.json ================================================ [ { "description": "patternProperties keyword should be used together with propertyNames", "schema": { "$ref": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#" }, "tests": [ { "description": "patternProperties keyword used without propertyNames is invalid", "data": { "patternProperties": { ".*": {} } }, "valid": false }, { "description": "patternProperties keyword used with propertyNames is valid", "data": { "patternProperties": { ".*": {} }, "propertyNames": { "maxLength": "256" } }, "valid": true } ] } ] ================================================ FILE: spec/security/string.json ================================================ [ { "description": "pattern keyword should be used together with maxLength", "schema": { "$ref": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#" }, "tests": [ { "description": "pattern keyword used without maxLength is invalid", "data": { "pattern": ".*" }, "valid": false }, { "description": "pattern keyword used with maxLength is valid", "data": { "pattern": ".*", "maxLength": "256" }, "valid": true } ] }, { "description": "format keyword should be used together with maxLength", "schema": { "$ref": "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/json-schema-secure.json#" }, "tests": [ { "description": "format keyword used without maxLength is invalid", "data": { "format": "email" }, "valid": false }, { "description": "format keyword used with maxLength is valid", "data": { "format": "email", "maxLength": "256" }, "valid": true } ] } ] ================================================ FILE: spec/security.spec.ts ================================================ import _Ajv from "./ajv" import getAjvInstances from "./ajv_instances" import {withStandalone} from "./ajv_standalone" import jsonSchemaTest = require("json-schema-test") import options from "./ajv_options" import {afterError, afterEach} from "./after_test" import chai from "./chai" const instances = getAjvInstances(_Ajv, options, { schemas: [require("../dist/refs/json-schema-secure.json")], strictTypes: false, }) instances.forEach((ajv) => (ajv.opts.code.source = true)) jsonSchemaTest(withStandalone(instances), { description: "Secure schemas tests of " + instances.length + " ajv instances with different options", suites: {security: require("./_json/security")}, assert: chai.assert, afterError, afterEach, cwd: __dirname, hideFolder: "security/", }) ================================================ FILE: spec/standalone.spec.ts ================================================ import type Ajv from "../dist/core" import type {AnyValidateFunction} from "../dist/core" import _Ajv from "./ajv" import standaloneCode from "../dist/standalone" import ajvFormats from "ajv-formats" import requireFromString = require("require-from-string") import {importFromStringSync} from "module-from-string" import assert = require("assert") function testExportTypeEsm(moduleCode: string, singleExport: boolean) { //Must have assert.strictEqual(moduleCode.includes("export const"), true) if (singleExport) { assert.strictEqual(moduleCode.includes("export default"), true) } //Must not have assert.strictEqual(moduleCode.includes("module.exports"), false) } function testExportTypeCjs(moduleCode: string, singleExport: boolean) { //Must have if (singleExport) { assert.strictEqual(moduleCode.includes("module.exports"), true) } else { assert.strictEqual(moduleCode.includes("exports.") || moduleCode.includes("exports["), true) } //Must not have assert.strictEqual(moduleCode.includes("export const"), false) } describe("standalone code generation", () => { describe("multiple exports", () => { let ajv: Ajv const numSchema = { $id: "https://example.com/number.json", type: "number", minimum: 0, } const strSchema = { $id: "https://example.com/string.json", type: "string", minLength: 2, } describe("without schema keys", () => { it("should generate module code with named export - CJS", () => { ajv = new _Ajv({code: {source: true}}) ajv.addSchema(numSchema) ajv.addSchema(strSchema) const moduleCode = standaloneCode(ajv, { validateNumber: "https://example.com/number.json", validateString: "https://example.com/string.json", }) testExportTypeCjs(moduleCode, false) const m = requireFromString(moduleCode) assert.strictEqual(Object.keys(m).length, 2) testExports(m) }) it("should generate module code with named export - ESM", () => { ajv = new _Ajv({code: {source: true, esm: true}}) ajv.addSchema(numSchema) ajv.addSchema(strSchema) const moduleCode = standaloneCode(ajv, { validateNumber: "https://example.com/number.json", validateString: "https://example.com/string.json", }) testExportTypeEsm(moduleCode, false) const m = importFromStringSync(moduleCode) assert.strictEqual(Object.keys(m).length, 2) testExports(m) }) it("should generate module code with all exports - CJS", () => { ajv = new _Ajv({code: {source: true}}) ajv.addSchema(numSchema) ajv.addSchema(strSchema) const moduleCode = standaloneCode(ajv) testExportTypeCjs(moduleCode, false) const m = requireFromString(moduleCode) assert.strictEqual(Object.keys(m).length, 2) testExports({ validateNumber: m["https://example.com/number.json"], validateString: m["https://example.com/string.json"], }) }) it("should generate module code with all exports - ESM", () => { ajv = new _Ajv({code: {source: true, esm: true}}) ajv.addSchema(numSchema) ajv.addSchema(strSchema) try { standaloneCode(ajv) } catch (err) { if (err instanceof Error) { const isMappingErr = `CodeGen: invalid export name: ${numSchema.$id}, use explicit $id name mapping` === err.message || `CodeGen: invalid export name: ${strSchema.$id}, use explicit $id name mapping` === err.message assert.strictEqual(isMappingErr, true) } else { throw err } } }) }) describe("with schema keys", () => { beforeEach(() => { ajv = new _Ajv({code: {source: true}}) ajv.addSchema(numSchema, "validateNumber") ajv.addSchema(strSchema, "validateString") }) it("should generate module code with named exports", () => { const moduleCode = standaloneCode(ajv, { validateNumber: "validateNumber", validateString: "validateString", }) const m = requireFromString(moduleCode) assert.strictEqual(Object.keys(m).length, 2) testExports(m) }) it("should generate module code with all exports", () => { const moduleCode = standaloneCode(ajv) const m = requireFromString(moduleCode) assert.strictEqual(Object.keys(m).length, 2) testExports(m) }) }) function testExports(m: {[n: string]: AnyValidateFunction}) { assert.strictEqual(m.validateNumber(1), true) assert.strictEqual(m.validateNumber(0), true) assert.strictEqual(m.validateNumber(-1), false) assert.strictEqual(m.validateNumber("1"), false) assert.strictEqual(m.validateString("123"), true) assert.strictEqual(m.validateString("12"), true) assert.strictEqual(m.validateString("1"), false) assert.strictEqual(m.validateString(12), false) } }) describe("issue #1361", () => { describe("two refs to the same schema", () => { const userSchema = { $id: "user.json", type: "object", properties: { name: {type: "string"}, }, required: ["name"], } const infoSchema = { $id: "info.json", type: "object", properties: { author: {$ref: "user.json"}, contributors: { type: "array", items: {$ref: "user.json"}, }, }, required: ["author", "contributors"], } describe("all exports", () => { it("should not have duplicate functions", () => { const ajv = new _Ajv({ allErrors: true, code: {optimize: false, source: true}, inlineRefs: false, // it is needed to show the issue, schemas with refs won't be inlined anyway schemas: [userSchema, infoSchema], }) const moduleCode = standaloneCode(ajv) assertNoDuplicateFunctions(moduleCode) const {"user.json": user, "info.json": info} = requireFromString(moduleCode) testExports({user, info}) }) }) describe("named exports", () => { it("should not have duplicate functions", () => { const ajv = new _Ajv({ allErrors: true, code: {optimize: false, source: true}, inlineRefs: false, // it is needed to show the issue, schemas with refs won't be inlined anyway schemas: [userSchema, infoSchema], }) const moduleCode = standaloneCode(ajv, {user: "user.json", info: "info.json"}) assertNoDuplicateFunctions(moduleCode) testExports(requireFromString(moduleCode)) }) }) }) describe("mutually recursive schemas", () => { const userSchema = { $id: "user.json", type: "object", properties: { name: {type: "string"}, infos: { type: "array", items: {$ref: "info.json"}, }, }, required: ["name"], } const infoSchema = { $id: "info.json", type: "object", properties: { author: {$ref: "user.json"}, contributors: { type: "array", items: {$ref: "user.json"}, }, }, required: ["author", "contributors"], } describe("all exports", () => { it("should not have duplicate functions", () => { const ajv = new _Ajv({ allErrors: true, code: {optimize: false, source: true}, inlineRefs: false, // it is needed to show the issue, schemas with refs won't be inlined anyway schemas: [userSchema, infoSchema], }) const moduleCode = standaloneCode(ajv) assertNoDuplicateFunctions(moduleCode) const {"user.json": user, "info.json": info} = requireFromString(moduleCode) testExports({user, info}) }) }) describe("named exports", () => { it("should not have duplicate functions", () => { const ajv = new _Ajv({ allErrors: true, code: {optimize: false, source: true}, inlineRefs: false, // it is needed to show the issue, schemas with refs won't be inlined anyway schemas: [userSchema, infoSchema], }) const moduleCode = standaloneCode(ajv, {user: "user.json", info: "info.json"}) assertNoDuplicateFunctions(moduleCode) testExports(requireFromString(moduleCode)) }) }) }) function assertNoDuplicateFunctions(code: string): void { const funcs = code.match(/function\s+([a-z0-9_$]+)/gi) assert(Array.isArray(funcs)) assert(funcs.length > 0) assert.strictEqual(funcs.length, new Set(funcs).size, "should have no duplicates") } function testExports(validate: {[n: string]: AnyValidateFunction}): void { assert.strictEqual(validate.user({}), false) assert.strictEqual(validate.user({name: "usr1"}), true) assert.strictEqual(validate.info({}), false) assert.strictEqual( validate.info({ author: {name: "usr1"}, contributors: [{name: "usr2"}], }), true ) } }) it("should generate module code with a single export - CJS", () => { const ajv = new _Ajv({code: {source: true}}) const v = ajv.compile({ type: "number", minimum: 0, }) const moduleCode = standaloneCode(ajv, v) testExportTypeCjs(moduleCode, true) const m = requireFromString(moduleCode) testExport(m) testExport(m.default) function testExport(validate: AnyValidateFunction) { assert.strictEqual(validate(1), true) assert.strictEqual(validate(0), true) assert.strictEqual(validate(-1), false) assert.strictEqual(validate("1"), false) } }) it("should generate module code with a single export - ESM", () => { const ajv = new _Ajv({code: {source: true, esm: true}}) const v = ajv.compile({ type: "number", minimum: 0, }) const moduleCode = standaloneCode(ajv, v) testExportTypeEsm(moduleCode, true) const m = importFromStringSync(moduleCode) testExport(m.validate) testExport(m.default) function testExport(validate: AnyValidateFunction) { assert.strictEqual(validate(1), true) assert.strictEqual(validate(0), true) assert.strictEqual(validate(-1), false) assert.strictEqual(validate("1"), false) } }) describe("standalone code with ajv-formats", () => { const schema = { $schema: "http://json-schema.org/draft-07/schema#", definitions: { User: { type: "object", properties: { email: { type: "string", format: "email", }, }, required: ["email"], additionalProperties: false, }, }, } it("should support formats with standalone code", () => { const ajv = new _Ajv({code: {source: true}}) ajvFormats(ajv) ajv.addSchema(schema) const moduleCode = standaloneCode(ajv, {validateUser: "#/definitions/User"}) const {validateUser} = requireFromString(moduleCode) assert(typeof validateUser == "function") assert.strictEqual(validateUser({}), false) assert.strictEqual(validateUser({email: "foo"}), false) assert.strictEqual(validateUser({email: "foo@bar.com"}), true) }) }) describe("standalone code with RegExp format", () => { const schema = { $schema: "http://json-schema.org/draft-07/schema#", definitions: { User: { type: "object", properties: { username: { type: "string", format: "username", }, }, required: ["username"], additionalProperties: false, }, }, } it("should support RegExp format with standalone code", () => { const ajv = new _Ajv({code: {source: true}}) ajv.addFormat("username", /[a-z][a-z0-9_]*/i) ajv.addSchema(schema) const moduleCode = standaloneCode(ajv, {validateUser: "#/definitions/User"}) const {validateUser} = requireFromString(moduleCode) assert(typeof validateUser == "function") assert.strictEqual(validateUser({}), false) assert.strictEqual(validateUser({username: "foo_bar"}), true) assert.strictEqual(validateUser({email: "foo bar"}), false) }) }) }) ================================================ FILE: spec/tests/issues/12_restoring_root_after_resolve.json ================================================ [ { "description": "restoring root after ref resolution (#12)", "schema": { "definitions": { "int": {"$ref": "http://localhost:1234/integer.json"}, "str": {"type": "string"} }, "anyOf": [{"$ref": "#/definitions/int"}, {"$ref": "#/definitions/str"}] }, "tests": [ { "description": "valid string", "data": "foo", "valid": true }, { "description": "valid number", "data": 1, "valid": true }, { "description": "invalid object", "data": {}, "valid": false } ] }, { "description": "all refs are in the same place", "schema": { "definitions": { "int": {"type": "integer"}, "str": {"type": "string"} }, "anyOf": [{"$ref": "#/definitions/int"}, {"$ref": "#/definitions/str"}] }, "tests": [ { "description": "valid string", "data": "foo", "valid": true }, { "description": "valid number", "data": 1, "valid": true }, { "description": "invalid object", "data": {}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/13_root_ref_in_ref_in_remote_ref.json ================================================ [ { "description": "root ref in remote ref (#13)", "schema": { "$id": "http://localhost:1234/issue13", "type": "object", "properties": { "name": {"$ref": "name.json#/definitions/orNull"} } }, "tests": [ { "description": "string is valid", "data": { "name": "foo" }, "valid": true }, { "description": "null is valid", "data": { "name": null }, "valid": true }, { "description": "object is invalid", "data": { "name": { "name": null } }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/14_ref_in_remote_ref_with_id.json ================================================ [ { "description": "ref in remote ref with ids", "schema": { "$id": "http://localhost:1234/issue14a.json", "type": "array", "items": {"$ref": "foo.json"} }, "tests": [ { "description": "string is valid", "data": [ { "bar": "any string" } ], "valid": true }, { "description": "not string is invalid", "data": [ { "bar": 1 } ], "valid": false } ] }, { "description": "remote ref in definitions in remote ref with ids (#14)", "schema": { "$id": "http://localhost:1234/issue14b.json", "type": "array", "items": {"$ref": "buu.json#/definitions/buu"} }, "tests": [ { "description": "string is valid", "data": [ { "bar": "any string" } ], "valid": true }, { "description": "not string is invalid", "data": [ { "bar": 1 } ], "valid": false } ] } ] ================================================ FILE: spec/tests/issues/1668_not_with_other_keywords.json ================================================ [ { "description": "not with allOf", "schema": { "allOf": [{"const": 1}], "not": {"const": true} }, "tests": [ { "description": "valid", "data": 1, "valid": true }, { "description": "invalid (const)", "data": 3, "valid": false }, { "description": "invalid (not)", "data": true, "valid": false } ] }, { "description": "not with anyOf", "schema": { "anyOf": [{"const": 1}], "not": {"const": true} }, "tests": [ { "description": "valid", "data": 1, "valid": true }, { "description": "invalid (const)", "data": 3, "valid": false }, { "description": "invalid (not)", "data": true, "valid": false } ] }, { "description": "not with oneOf", "schema": { "oneOf": [{"const": 1}], "not": {"const": true} }, "tests": [ { "description": "valid", "data": 1, "valid": true }, { "description": "invalid (const)", "data": 3, "valid": false }, { "description": "invalid (not)", "data": true, "valid": false } ] }, { "description": "not with properties", "schema": { "not": { "properties": { "foo": {"const": true} } }, "properties": { "foo": {"const": 1} } }, "tests": [ { "description": "valid", "data": {"foo": 1}, "valid": true }, { "description": "invalid (const)", "data": {"foo": 3}, "valid": false }, { "description": "invalid (not)", "data": {"foo": true}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/170_ref_and_id_in_sibling.json ================================================ [ { "description": "sibling property has id (#170)", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_object_1", "type": "object", "properties": { "title": { "$id": "http://example.com/title", "type": "string" }, "file": {"$ref": "#/definitions/file-entry"} }, "definitions": { "file-entry": {"type": "string"} } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_object_2", "type": "object", "properties": { "title": { "$id": "http://example.com/title", "type": "string" }, "file": {"$ref": "#/definitions/file-entry"} }, "definitions": { "file-entry": {"type": "string"} } } ], "tests": [ { "description": "valid object", "data": { "title": "foo", "file": "bar" }, "valid": true }, { "description": "invalid object", "data": { "title": "foo", "file": 2 }, "valid": false } ] }, { "description": "sibling item has id", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_array_1", "type": "array", "items": [ { "$id": "http://example.com/0", "type": "string" }, {"$ref": "#/definitions/file-entry"} ], "definitions": { "file-entry": {"type": "string"} } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_array_2", "type": "array", "items": [ { "$id": "http://example.com/0", "type": "string" }, {"$ref": "#/definitions/file-entry"} ], "definitions": { "file-entry": {"type": "string"} } } ], "tests": [ { "description": "valid array", "data": ["foo", "bar"], "valid": true }, { "description": "invalid array", "data": ["foo", 2], "valid": false } ] }, { "description": "sibling schema in anyOf has id", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_anyof_1", "anyOf": [ { "$id": "http://example.com/0", "type": "number" }, {"$ref": "#/definitions/def"} ], "definitions": { "def": {"type": "string"} } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_anyof_2", "anyOf": [ { "$id": "http://example.com/0", "type": "number" }, {"$ref": "#/definitions/def"} ], "definitions": { "def": {"type": "string"} } } ], "tests": [ { "description": "valid string", "data": "foo", "valid": true }, { "description": "valid number", "data": 1, "valid": true }, { "description": "invalid object", "data": {}, "valid": false } ] }, { "description": "sibling schema in oneOf has id", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_oneof_1", "oneOf": [ { "$id": "http://example.com/0", "type": "number" }, {"$ref": "#/definitions/def"} ], "definitions": { "def": {"type": "string"} } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_oneof_2", "oneOf": [ { "$id": "http://example.com/0", "type": "number" }, {"$ref": "#/definitions/def"} ], "definitions": { "def": {"type": "string"} } } ], "tests": [ { "description": "valid string", "data": "foo", "valid": true }, { "description": "valid number", "data": 1, "valid": true }, { "description": "invalid object", "data": {}, "valid": false } ] }, { "description": "sibling schema in allOf has id", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_allof_1", "allOf": [ { "$id": "http://example.com/0", "type": "string", "maxLength": 3 }, {"$ref": "#/definitions/def"} ], "definitions": { "def": {"type": "string"} } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_allof_2", "allOf": [ { "$id": "http://example.com/0", "type": "string", "maxLength": 3 }, {"$ref": "#/definitions/def"} ], "definitions": { "def": {"type": "string"} } } ], "tests": [ { "description": "valid string", "data": "foo", "valid": true }, { "description": "invalid string", "data": "quux", "valid": false } ] }, { "description": "sibling schema in dependencies has id", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_dependencies_1", "type": "object", "dependencies": { "foo": { "$id": "http://example.com/foo", "required": ["bar"] }, "bar": {"$ref": "#/definitions/def"} }, "definitions": { "def": {"required": ["baz"]} } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/base_dependencies_2", "type": "object", "dependencies": { "foo": { "$id": "http://example.com/foo", "required": ["bar"] }, "bar": {"$ref": "#/definitions/def"} }, "definitions": { "def": {"required": ["baz"]} } } ], "tests": [ { "description": "valid object", "data": {"foo": 1, "bar": 2, "baz": 3}, "valid": true }, { "description": "invalid object 2", "data": {"foo": 1}, "valid": false }, { "description": "invalid object 2", "data": {"foo": 1, "bar": 2}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/17_escaping_pattern_property.json ================================================ [ { "description": "escaping pattern property (#17)", "schema": { "type": "object", "patternProperties": { "^.+$": { "type": "object", "required": ["unit"] } }, "additionalProperties": false }, "tests": [ { "description": "empty object", "data": {}, "valid": true } ] } ] ================================================ FILE: spec/tests/issues/19_required_many_properties.json ================================================ [ { "description": "Required for many properties in inner level (#19)", "schema": { "type": "array", "items": { "type": "object", "required": [ "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16", "p17", "p18", "p19", "p20", "p21", "p22" ] } }, "tests": [ { "description": "valid", "data": [ { "p1": "test", "p2": "test", "p3": "test", "p4": "test", "p5": "test", "p6": "test", "p7": "test", "p8": "test", "p9": "test", "p10": "test", "p11": "test", "p12": "test", "p13": "test", "p14": "test", "p15": "test", "p16": "test", "p17": "test", "p18": "test", "p19": "test", "p20": "test", "p21": "test", "p22": "test" } ], "valid": true }, { "description": "invalid", "data": [ { "p2": "test", "p3": "test", "p4": "test", "p5": "test", "p6": "test", "p7": "test", "p8": "test", "p9": "test", "p10": "test", "p11": "test", "p12": "test", "p13": "test", "p14": "test", "p15": "test", "p16": "test", "p17": "test", "p18": "test", "p19": "test", "p20": "test", "p21": "test", "p22": "test" } ], "valid": false } ] } ] ================================================ FILE: spec/tests/issues/1_ids_in_refs.json ================================================ [ { "description": "IDs in refs without root id (#1)", "schemas": [ { "definitions": { "int": { "$id": "#int", "type": "integer" } }, "$ref": "#int" }, { "definitions": { "int": { "$id": "#int", "type": "integer" } }, "$ref": "#int" } ], "tests": [ {"description": "valid", "data": 1, "valid": true}, {"description": "invalid", "data": "foo", "valid": false} ] }, { "description": "IDs in refs with root id", "schemas": [ { "$id": "http://example.com/int_1.json", "definitions": { "int": { "$id": "#int", "type": "integer" } }, "$ref": "#int" }, { "$id": "http://example.com/int_2.json", "definitions": { "int": { "$id": "#int", "type": "integer" } }, "$ref": "#int" } ], "tests": [ {"description": "valid", "data": 1, "valid": true}, {"description": "invalid", "data": "foo", "valid": false} ] }, { "description": "Definitions instead of IDs", "schema": { "definitions": { "int": { "type": "integer" } }, "$ref": "#/definitions/int" }, "tests": [ {"description": "valid", "data": 1, "valid": true}, {"description": "invalid", "data": "foo", "valid": false} ] } ] ================================================ FILE: spec/tests/issues/20_failing_to_parse_schema.json ================================================ [ { "description": "Failing to parse schema with required property that is not an identifier (#20)", "schema": { "type": "object", "required": ["a-b", "a'", "a\""] }, "tests": [ { "description": "valid", "data": { "a-b": "test", "a'": "test", "a\"": "test" }, "valid": true }, { "description": "invalid", "data": {}, "valid": false } ] }, { "description": "Failing to parse schema with required property that is not an identifier for many properties (#20)", "schema": { "type": "object", "required": [ "a-1", "a-2", "a-3", "a-4", "a-5", "a-6", "a-7", "a-8", "a-9", "a-10", "a-11", "a-12", "a-13", "a-14", "a-15", "a-16", "a-17", "a-18", "a-19", "a-20", "a-21", "a-22", "'", "\"" ] }, "tests": [ { "description": "valid", "data": { "a-1": "test", "a-2": "test", "a-3": "test", "a-4": "test", "a-5": "test", "a-6": "test", "a-7": "test", "a-8": "test", "a-9": "test", "a-10": "test", "a-11": "test", "a-12": "test", "a-13": "test", "a-14": "test", "a-15": "test", "a-16": "test", "a-17": "test", "a-18": "test", "a-19": "test", "a-20": "test", "a-21": "test", "a-22": "test", "'": "test", "\"": "test" }, "valid": true }, { "description": "invalid", "data": {}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/226_json_with_control_chars.json ================================================ [ { "description": "JSON with control characters - 'properties' (#226)", "schema": { "properties": { "foo\nbar": {"type": "number"}, "foo\"bar": {"type": "number"}, "foo\\bar": {"type": "number"}, "foo\rbar": {"type": "number"}, "foo\tbar": {"type": "number"}, "foo\fbar": {"type": "number"} } }, "tests": [ { "description": "object with all numbers is valid", "data": { "foo\nbar": 1, "foo\"bar": 1, "foo\\bar": 1, "foo\rbar": 1, "foo\tbar": 1, "foo\fbar": 1 }, "valid": true }, { "description": "object with strings is invalid", "data": { "foo\nbar": "1", "foo\"bar": "1", "foo\\bar": "1", "foo\rbar": "1", "foo\tbar": "1", "foo\fbar": "1" }, "valid": false } ] }, { "description": "JSON with control characters - 'required' (#226)", "schema": { "required": ["foo\nbar", "foo\"bar", "foo\\bar", "foo\rbar", "foo\tbar", "foo\fbar"] }, "tests": [ { "description": "object with all properties present is valid", "data": { "foo\nbar": 1, "foo\"bar": 1, "foo\\bar": 1, "foo\rbar": 1, "foo\tbar": 1, "foo\fbar": 1 }, "valid": true }, { "description": "object with some properties missing is invalid", "data": { "foo\nbar": "1", "foo\"bar": "1" }, "valid": false } ] }, { "description": "JSON with control characters - 'enum'", "schema": { "enum": ["foo\nbar", "foo\rbar"] }, "tests": [ { "description": "member 1 is valid", "data": "foo\nbar", "valid": true }, { "description": "member 2 is valid", "data": "foo\rbar", "valid": true }, { "description": "another string is invalid", "data": "abc", "valid": false } ] }, { "description": "JSON with control characters - 'dependencies'", "schema": { "dependencies": { "foo\nbar": ["foo\rbar"], "foo\tbar": { "minProperties": 4 } } }, "tests": [ { "description": "valid object 1", "data": { "foo\nbar": 1, "foo\rbar": 2 }, "valid": true }, { "description": "valid object 2", "data": { "foo\tbar": 1, "a": 2, "b": 3, "c": 4 }, "valid": true }, { "description": "invalid object 1", "data": { "foo\nbar": 1, "foo": 2 }, "valid": false }, { "description": "invalid object 2", "data": { "foo\tbar": 1, "a": 2 }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/27_1_recursive_raml_schema.json ================================================ [ { "description": "JSON Schema for a standard RAML object (#27)", "schema": { "title": "A JSON Schema for a standard RAML object", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["title"], "properties": { "title": { "type": "string", "description": "The title property is a short plain text description of the RESTful API. The title property's value SHOULD be suitable for use as a title for the contained user documentation." }, "version": { "type": "string", "description": "If the RAML API definition is targeted to a specific API version, the API definition MUST contain a version property." }, "baseUri": { "type": "string", "format": "uri", "description": "A RESTful API's resources are defined relative to the API's base URI. The use of the baseUri field is OPTIONAL to allow describing APIs that have not yet been implemented." }, "baseUriParameters": { "$ref": "#/definitions/namedParameters" }, "mediaType": { "$ref": "#/definitions/mediaType" }, "protocols": { "$ref": "#/definitions/protocols" }, "securitySchemes": { "$ref": "#/definitions/securitySchemes" }, "securedBy": { "$ref": "#/definitions/securedBy" }, "documentation": { "$ref": "#/definitions/documentation" }, "resources": { "$ref": "#/definitions/rootResource" }, "traits": { "$ref": "#/definitions/traits" }, "resourceTypes": { "$ref": "#/definitions/resourceTypes" } }, "definitions": { "namedParameters": { "patternProperties": { "^[\\w-]+$": { "oneOf": [ { "$ref": "#/definitions/namedParameter" }, { "type": "array", "items": { "$ref": "#/definitions/namedParameter" } } ] } }, "description": "This RAML Specification describes collections of named parameters for the following properties: URI parameters, query string parameters, form parameters, request bodies (depending on the media type), and request and response headers. Read more: https://github.com/raml-org/raml-spec/blob/master/raml-0.8.md#named-parameters" }, "namedParameter": { "type": "object", "properties": { "displayName": { "type": "string", "description": "The displayName attribute specifies the parameter's display name. It is a friendly name used only for display or documentation purposes. If displayName is not specified, it defaults to the property's key (the name of the property itself)." }, "description": { "type": "string", "description": "The description attribute describes the intended use or meaning of the parameter. This value MAY be formatted using Markdown." }, "type": { "type": "string", "enum": ["string", "number", "integer", "date", "boolean", "file"], "default": "string", "description": "The type attribute specifies the primitive type of the parameter's resolved value. If the type is not specified, it defaults to string. API clients MUST return/throw an error if the parameter's resolved value does not match the specified type." }, "enum": { "$ref": "#/definitions/enum", "description": "The enum attribute provides an enumeration of the parameter's valid values. This MUST be an array. If the enum attribute is defined, API clients and servers MUST verify that a parameter's value matches a value in the enum array. If there is no matching value, the clients and servers MUST treat this as an error." }, "pattern": { "type": "string", "format": "regex", "description": "The pattern attribute is a regular expression that a parameter of type string MUST match. Regular expressions MUST follow the regular expression specification from ECMA 262/Perl 5." }, "minLength": { "$ref": "#/definitions/minIntegerDefault0", "description": "The minLength attribute specifies the parameter value's minimum number of characters." }, "maxLength": { "$ref": "#/definitions/minInteger", "description": "The maxLength attribute specifies the parameter value's maximum number of characters." }, "minimum": { "type": "number", "description": "The minimum attribute specifies the parameter's minimum value." }, "maximum": { "type": "number", "description": "The maximum attribute specifies the parameter's maximum value." }, "example": { "$ref": "#/definitions/primitiveType", "description": "The example attribute shows an example value for the property. This can be used, e.g., by documentation generators to generate sample values for the property." }, "repeat": { "$ref": "#/definitions/booleanDefaultFalse", "description": "The repeat attribute specifies that the parameter can be repeated. If the parameter can be used multiple times, the repeat parameter value MUST be set to true. Otherwise, the default value is false and the parameter may not be repeated." }, "required": { "$ref": "#/definitions/booleanDefaultFalse", "description": "The required attribute specifies whether the parameter and its value MUST be present in the API definition. It must be either 'true' if the value MUST be present or false otherwise. arameters are optional unless the required attribute is included and its value set to true. For a URI parameter, the required attribute MAY be omitted, but its default value is true." }, "default": { "$ref": "#/definitions/primitiveType", "default": "The default attribute specifies the default value to use for the property if the property is omitted or its value is not specified. This SHOULD NOT be interpreted as a requirement for the client to send the default attribute's value if there is no other value to send. Instead, the default attribute's value is the value the server uses if the client does not send a value." } }, "additionalProperties": false }, "mediaType": { "type": "string", "pattern": "^[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+/[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+$", "description": "The media types returned by API responses, and expected from API requests that accept a body, MAY be defaulted by specifying the mediaType property." }, "protocols": { "type": "array", "minItems": 1, "items": { "type": "string", "enum": ["HTTP", "HTTPS"] }, "uniqueItems": true, "description": "A RESTful API can be reached via HTTP, HTTPS, or both. The protocols property MAY be used to specify the protocols that an API supports. If the protocols property is not specified, the protocol specified at the baseUri property is used." }, "securitySchemes": { "type": "object", "patternProperties": { "^[\\w-]+$": { "$ref": "#/definitions/securityScheme" } }, "additionalProperties": false, "description": "The securitySchemes property MUST be used to specify an API's security mechanisms, including the required settings and the authentication methods that the API supports. one authentication method is allowed if the API supports them." }, "securityScheme": { "type": "object", "properties": { "type": { "type": "string", "description": "The type attribute MAY be used to convey information about authentication flows and mechanisms to processing applications such as Documentation Generators and Client generators." }, "description": { "type": "string", "description": "The description attribute MAY be used to describe a securitySchemes property." }, "describedBy": { "$ref": "#/definitions/describedBy" }, "settings": { "type": "object", "description": "The settings attribute MAY be used to provide security schema-specific information. Depending on the value of the type parameter, its attributes can vary." } }, "oneOf": [ { "type": "object", "required": ["type"], "properties": { "type": { "enum": ["OAuth 1.0"] }, "settings": { "type": "object", "required": ["requestTokenUri", "authorizationUri", "tokenCredentialsUri"], "properties": { "requestTokenUri": { "type": "string", "format": "uri" }, "authorizationUri": { "type": "string", "format": "uri" }, "tokenCredentialsUri": { "type": "string", "format": "uri" } } } } }, { "type": "object", "required": ["type"], "properties": { "type": { "enum": ["OAuth 2.0"] }, "settings": { "type": "object", "required": ["authorizationUri", "accessTokenUri", "authorizationGrants"], "properties": { "authorizationUri": { "type": "string", "format": "uri" }, "accessTokenUri": { "type": "string", "format": "uri" }, "authorizationGrants": { "type": "array", "items": { "enum": ["code", "token", "owner", "credentials"] } }, "scopes": { "type": "array", "items": { "type": "string" } } } } } }, { "type": "object", "required": ["type"], "properties": { "type": { "oneOf": [ { "enum": ["Basic Authentication", "Digest Authentication"] }, { "type": "string", "pattern": "^x-" } ] } } } ], "additionalProperties": false }, "traits": { "type": "object", "patternProperties": { "^[\\w-]+$": { "$ref": "#/definitions/trait" } }, "additionalProperties": false }, "describedBy": { "type": "object", "properties": { "description": { "type": "string" }, "queryParameters": { "$ref": "#/definitions/namedParameters" }, "headers": { "$ref": "#/definitions/namedParameters" }, "responses": { "$ref": "#/definitions/responses" }, "body": { "$ref": "#/definitions/body" } }, "additionalProperties": false, "description": "The describedBy attribute MAY be used to apply a trait-like structure to a security scheme mechanism so as to extend the mechanism, such as specifying response codes, HTTP headers or custom documentation." }, "trait": { "type": "object", "properties": { "description": { "type": "string" }, "queryParameters": { "$ref": "#/definitions/namedParameters" }, "headers": { "$ref": "#/definitions/namedParameters" }, "responses": { "$ref": "#/definitions/responses" }, "body": { "$ref": "#/definitions/body" }, "securedBy": { "$ref": "#/definitions/securedBy" } }, "additionalProperties": false, "description": "A trait is a partial method definition that, like a method, can provide method-level properties such as description, headers, query string parameters, and responses. Methods that use one or more traits inherit those traits' properties." }, "resourceTypes": { "type": "object", "patternProperties": { "^[\\w-]+$": { "$ref": "#/definitions/resourceType" } } }, "resourceType": { "type": "object", "properties": { "is": { "$ref": "#/definitions/is" }, "type": { "$ref": "#/definitions/reference" } }, "patternProperties": { "^(?:head|get|post|put|patch|delete|options|trace|connect)\\??$": { "$ref": "#/definitions/method" } }, "additionalProperties": false, "description": "A resource type is a partial resource definition that, like a resource, can specify a description and methods and their properties. Resources that use a resource type inherit its properties, such as its methods." }, "body": { "type": "object", "properties": { "schema": { "type": "string" }, "formParameters": { "$ref": "#/definitions/namedParameters" }, "application/x-www-form-urlencoded": { "$ref": "#/definitions/formBody" }, "multipart/form-data": { "$ref": "#/definitions/formBody" }, "application/json": { "$ref": "#/definitions/schemaBody" }, "text/xml": { "$ref": "#/definitions/schemaBody" } }, "patternProperties": { "^[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+/[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+$": { "$ref": "#/definitions/standardResponseBody" } }, "additionalProperties": false }, "formBody": { "$ref": "#/definitions/responseBody", "properties": { "formParameters": { "$ref": "#/definitions/namedParameters" } }, "additionalProperties": false, "description": "Web forms REQUIRE special encoding and custom declaration." }, "schemaBody": { "$ref": "#/definitions/responseBody", "properties": { "schema": { "type": "string" } }, "additionalProperties": false, "description": "The structure of a request or response body MAY be further specified by the schema property under the appropriate media type." }, "standardResponseBody": { "$ref": "#/definitions/responseBody", "additionalProperties": false }, "responseBody": { "type": ["null", "object"], "properties": { "example": { "type": "string" } } }, "responses": { "type": "object", "patternProperties": { "^\\d{3}$": { "$ref": "#/definitions/response" } }, "additionalProperties": false }, "response": { "type": "object", "properties": { "description": { "type": "string" }, "headers": { "$ref": "#/definitions/namedParameters", "description": "An API's methods may support custom header values in responses. The custom, non-standard HTTP headers MUST be specified by the headers property." }, "body": { "$ref": "#/definitions/body" } }, "additionalProperties": false }, "securedBy": { "$ref": "#/definitions/enum", "items": { "oneOf": [ { "type": "null" }, { "$ref": "#/definitions/reference" } ] }, "description": "A securityScheme may also be applied to a resource by using the securedBy key, which is equivalent to applying the securityScheme to all methods that may be declared, explicitly or implicitly, by defining the resourceTypes or traits property for that resource." }, "reference": { "oneOf": [ { "type": "string", "pattern": "^[\\w-]+$" }, { "type": "object", "minProperties": 1, "maxProperties": 1, "patternProperties": { "^[\\w-]+$": { "type": "object" } } } ] }, "documentation": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "string" }, "content": { "type": "string" } }, "additionalProperties": false }, "description": "The API definition can include a variety of documents that serve as a user guides and reference documentation for the API. Such documents can clarify how the API works or provide business context." }, "rootResource": { "type": "object", "patternProperties": { "^/": { "$ref": "#/definitions/resource" } }, "additionalProperties": false }, "resource": { "type": "object", "properties": { "displayName": { "type": "string", "description": "The displayName attribute provides a friendly name to the resource and can be used by documentation generation tools. The displayName key is OPTIONAL. If the displayName attribute is not defined for a resource, documentation tools SHOULD refer to the resource by its property key (i.e. its relative URI, e.g., \"/jobs\"), which acts as the resource's name." }, "description": { "type": "string", "description": "Each resource, whether top-level or nested, MAY contain a description property that briefly describes the resource. It is RECOMMENDED that all the API definition's resources includes the description property." }, "uriParameters": { "$ref": "#/definitions/namedParameters" }, "is": { "$ref": "#/definitions/is" }, "type": { "$ref": "#/definitions/reference" } }, "patternProperties": { "^(?:head|get|post|put|patch|delete|options|trace|connect)$": { "$ref": "#/definitions/method" }, "^/": { "$ref": "#/definitions/resource" } }, "additionalProperties": false }, "method": { "type": "object", "properties": { "description": { "type": "string", "description": "Each declared method MAY contain a description attribute that briefly describes what the method does to the resource. It is RECOMMENDED that all API definition methods include the description property." }, "queryParameters": { "$ref": "#/definitions/namedParameters", "description": "An API's resources MAY be filtered (to return a subset of results) or altered (such as transforming a response body from JSON to XML format) by the use of query strings. If the resource or its method supports a query string, the query string MUST be defined by the queryParameters property." }, "headers": { "$ref": "#/definitions/namedParameters", "description": "An API's methods MAY support or require non-standard HTTP headers. In the API definition, specify the non-standard HTTP headers by using the headers property." }, "protocols": { "$ref": "#/definitions/protocols", "description": "A method can override an API's protocols value for that single method by setting a different value for the fields." }, "responses": { "$ref": "#/definitions/responses", "description": "Resource methods MAY have one or more responses. Responses MAY be described using the description property, and MAY include example attributes or schema properties." }, "body": { "$ref": "#/definitions/body", "description": "Some method verbs expect the resource to be sent as a request body. For example, to create a resource, the request must include the details of the resource to create. Resources CAN have alternate representations. For example, an API might support both JSON and XML representations." }, "securedBy": { "$ref": "#/definitions/securedBy" }, "is": { "$ref": "#/definitions/is" } }, "additionalProperties": false }, "is": { "$ref": "#/definitions/enum", "items": { "$ref": "#/definitions/reference" } }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "minInteger": { "type": "integer", "minimum": 0 }, "minIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/minInteger" }, { "default": 0 } ] }, "booleanDefaultFalse": { "type": "boolean", "default": false }, "date": { "type": "string", "pattern": "^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat), \\d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$" }, "primitiveType": { "type": ["boolean", "integer", "number", "string"] } }, "additionalProperties": false }, "tests": [ { "description": "empty object is invalid", "data": {}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/27_recursive_reference.json ================================================ [ { "description": "Recursive reference (#27)", "schemas": [ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "testrec_1", "type": "object", "properties": { "layout": { "$id": "layout", "type": "object", "properties": { "layout": {"type": "string"}, "panels": { "type": "array", "items": { "anyOf": [{"type": "string"}, {"$ref": "layout"}] } } }, "required": ["layout", "panels"] } } }, { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "testrec_2", "type": "object", "properties": { "layout": { "$id": "layout", "type": "object", "properties": { "layout": {"type": "string"}, "panels": { "type": "array", "items": { "anyOf": [{"type": "string"}, {"$ref": "layout"}] } } }, "required": ["layout", "panels"] } } } ], "tests": [ { "description": "empty object is valid", "data": {}, "valid": true }, { "description": "valid object", "data": { "layout": { "layout": "test1", "panels": [ "panel1", { "layout": "test2", "panels": [ "panel2", { "layout": "test3", "panels": ["panel3"] } ] } ] } }, "valid": true }, { "description": "invalid object", "data": { "layout": { "layout": "test1", "panels": [ "panel1", { "layout": "test2", "panels": [ "panel2", { "layout": "test3", "panels": [3] } ] } ] } }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/28_escaping_pattern_error.json ================================================ [ { "description": "escaping pattern error (#28)", "schema": { "type": "object", "properties": { "mediaType": { "type": "string", "pattern": "^[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+/[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+$" } } }, "tests": [ { "description": "empty object", "data": {}, "valid": true } ] } ] ================================================ FILE: spec/tests/issues/2_root_ref_in_ref.json ================================================ [ { "description": "root ref in ref (#2)", "schema": { "definitions": { "arr": { "type": "array", "items": {"$ref": "#"} } }, "type": "object", "properties": { "name": {"type": "string"}, "children": {"$ref": "#/definitions/arr"} } }, "tests": [ { "description": "valid", "data": { "name": "foo", "children": [{"name": "bar"}, {"name": "baz"}] }, "valid": true }, { "description": "child numbers are invalid", "data": { "name": "foo", "children": [{"name": 1}, {"name": 2}] }, "valid": false }, { "description": "child arrays are invalid", "data": { "name": "foo", "children": [[], []] }, "valid": false } ] }, { "description": "root ref in ref with anyOf (#2)", "schema": { "definitions": { "orNull": { "anyOf": [{"type": "null"}, {"$ref": "#"}] } }, "type": "object", "properties": { "name": {"type": "string"}, "parent": {"$ref": "#/definitions/orNull"} } }, "tests": [ { "description": "null parent is valid", "data": { "name": "foo", "parent": null }, "valid": true }, { "skip": false, "description": "object parent is valid", "data": { "name": "foo", "parent": { "name": "bar", "parent": null } }, "valid": true }, { "description": "object parent is valid", "data": { "name": "foo", "parent": { "name": "bar", "parent": { "name": "baz", "parent": null } } }, "valid": true }, { "description": "string parent is invalid", "data": { "name": "foo", "parent": "buu" }, "valid": false }, { "description": "string subparent is invalid", "data": { "name": "foo", "parent": { "name": "bar", "parent": "baz" } }, "valid": false }, { "description": "string sub-subparent is invalid", "data": { "name": "foo", "parent": { "name": "bar", "parent": { "name": "baz", "parent": "quux" } } }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/311_quotes_in_refs.json ================================================ [ { "description": "quotes in refs (#311)", "schema": { "properties": { "foo\"bar": {"$ref": "#/definitions/foo\"bar"} }, "definitions": { "foo\"bar": {"type": "number"} } }, "tests": [ { "description": "object with all numbers is valid", "data": { "foo\"bar": 1, "foo\\bar": 1, "foo\nbar": 1, "foo\rbar": 1, "foo\tbar": 1, "foo\fbar": 1 }, "valid": true }, { "description": "object with strings is invalid", "data": { "foo\"bar": "1", "foo\\bar": "1", "foo\nbar": "1", "foo\rbar": "1", "foo\tbar": "1", "foo\fbar": "1" }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/33_json_schema_latest.json ================================================ [ { "description": "use latest json schema as v4 (#33)", "schema": { "$schema": "http://json-schema.org/schema", "type": "object", "properties": { "username": { "type": "string" } } }, "tests": [ { "description": "empty object", "data": {}, "valid": true } ] } ] ================================================ FILE: spec/tests/issues/413_dependencies_with_quote.json ================================================ [ { "description": "JSON with control characters - 'dependencies'", "schema": { "dependencies": { "foo'bar": { "not": {"required": ["bar"]} } } }, "tests": [ { "description": "valid object", "data": { "foo'bar": 1 }, "valid": true }, { "description": "invalid object", "data": { "foo'bar": 1, "bar": 2 }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/490_integer_validation.json ================================================ [ { "description": "integer validation (#490)", "schema": { "type": "integer", "minimum": 0 }, "tests": [ { "description": "valid integer", "data": 1, "valid": true }, { "description": "invalid integer", "data": -1, "valid": false }, { "description": "non-integer number is invalid", "data": 1.1, "valid": false }, { "description": "string is invalid", "data": "foo", "valid": false } ] } ] ================================================ FILE: spec/tests/issues/502_contains_empty_array_with_ref_in_another_property.json ================================================ [ { "description": "\"contains\" allows empty array when ref is used in sibling property (#502)", "schema": { "type": "object", "properties": { "str": {"$ref": "#/definitions/str"}, "arr": { "type": "array", "contains": {"type": "number"} } }, "definitions": { "str": {"type": "string"} } }, "tests": [ { "description": "valid object 1", "data": { "str": "a", "arr": [1] }, "valid": true }, { "description": "valid object 2", "data": { "arr": [1] }, "valid": true }, { "description": "invalid object 1", "data": { "str": "a", "arr": ["b"] }, "valid": false }, { "description": "invalid object 2", "data": { "arr": ["b"] }, "valid": false }, { "description": "invalid object 3", "data": { "arr": [] }, "valid": false }, { "description": "invalid object 4 (fails in #502)", "data": { "str": "a", "arr": [] }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/5_adding_dependency_after.json ================================================ [ { "description": "Adding dependency after dependent schema (#5)", "schema": "http://localhost:1234/second.json", "tests": [ { "description": "valid object", "data": {"first": "foo"}, "valid": true }, { "description": "valid object", "data": {"first": 1}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/5_recursive_references.json ================================================ [ { "description": "Recursive references between schemas (#5)", "schema": "http://localhost:1234/tree.json", "tests": [ { "description": "valid tree", "data": { "meta": "root", "nodes": [ { "value": 1, "subtree": { "meta": "child", "nodes": [{"value": 1.1}, {"value": 1.2}] } }, { "value": 2, "subtree": { "meta": "child", "nodes": [{"value": 2.1}, {"value": 2.2}] } } ] }, "valid": true }, { "description": "invalid tree", "data": { "meta": "root", "nodes": [ { "value": 1, "subtree": { "meta": "child", "nodes": [{"value": "string is invalid"}, {"value": 1.2}] } }, { "value": 2, "subtree": { "meta": "child", "nodes": [{"value": 2.1}, {"value": 2.2}] } } ] }, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/62_resolution_scope_change.json ================================================ [ { "description": "change resolution scope - change filename (#62)", "schema": { "type": "object", "properties": { "title": { "$ref": "http://localhost:1234/scope_foo.json#/definitions/bar" } } }, "tests": [ { "description": "string is valid", "data": {"title": "baz"}, "valid": true }, { "description": "number is invalid", "data": {"title": 1}, "valid": false } ] }, { "description": "resolution scope change - change folder (#62)", "schema": { "type": "object", "properties": { "list": { "$ref": "http://localhost:1234/scope_change.json#/definitions/baz" } } }, "tests": [ { "description": "number is valid", "data": {"list": [1]}, "valid": true }, { "description": "string is invalid", "data": {"list": ["a"]}, "valid": false } ] }, { "description": "resolution scope change - change folder in subschema (#62)", "schema": { "type": "object", "properties": { "list": { "$ref": "http://localhost:1234/scope_change.json#/definitions/baz/bar" } } }, "tests": [ { "description": "number is valid", "data": {"list": [1]}, "valid": true }, { "description": "string is invalid", "data": {"list": ["a"]}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/63_id_property_not_in_schema.json ================================================ [ { "description": "id property in referenced schema in object that is not a schema (#63)", "schema": { "type": "object", "properties": { "title": { "$ref": "http://json-schema.org/draft-07/schema#/properties/title" } } }, "tests": [ { "description": "empty object is valid", "data": {}, "valid": true }, { "description": "string is valid", "data": {"title": "foo"}, "valid": true }, { "description": "number is invalid", "data": {"title": 1}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/70_1_recursive_hash_ref_in_remote_ref.json ================================================ [ { "description": "hash ref inside hash ref in remote ref (#70, was passing)", "schema": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "tests": [ {"data": 1, "valid": true, "description": "positive integer is valid"}, {"data": 0, "valid": true, "description": "zero is valid"}, {"data": -1, "valid": false, "description": "negative integer is invalid"} ] }, { "description": "hash ref inside hash ref in remote ref with id (#70, was passing)", "schema": { "$id": "http://example.com/my_schema_2.json", "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "tests": [ {"data": 1, "valid": true, "description": "positive integer is valid"}, {"data": 0, "valid": true, "description": "zero is valid"}, {"data": -1, "valid": false, "description": "negative integer is invalid"} ] }, { "description": "local hash ref with remote hash ref without inner hash ref (#70, was passing)", "schema": { "definitions": { "a": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" } }, "properties": { "b": {"$ref": "#/definitions/a"} } }, "tests": [ { "data": {"b": 1}, "valid": true, "description": "positive integer is valid" }, {"data": {"b": 0}, "valid": true, "description": "zero is valid"}, { "data": {"b": -1}, "valid": false, "description": "negative integer is invalid" } ] }, { "description": "local hash ref with remote hash ref that has inner hash ref (#70)", "schema": { "definitions": { "a": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" } }, "properties": { "b": {"$ref": "#/definitions/a"} } }, "tests": [ { "data": {"b": 1}, "valid": true, "description": "positive integer is valid" }, {"data": {"b": 0}, "valid": true, "description": "zero is valid"}, { "data": {"b": -1}, "valid": false, "description": "negative integer is invalid" } ] } ] ================================================ FILE: spec/tests/issues/70_swagger_schema.json ================================================ [ { "description": "Swagger api schema does not compile (#70)", "schema": { "title": "A JSON Schema for Swagger 2.0 API.", "$id": "http://swagger.io/v2/schema.json#", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["swagger", "info", "paths"], "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "swagger": { "type": "string", "enum": ["2.0"], "description": "The Swagger version of this document." }, "info": { "$ref": "#/definitions/info" }, "host": { "type": "string", "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", "description": "The host (name or ip) of the API. Example: 'swagger.io'" }, "basePath": { "type": "string", "pattern": "^/", "description": "The base path to the API. Example: '/api'." }, "schemes": { "$ref": "#/definitions/schemesList" }, "consumes": { "description": "A list of MIME types accepted by the API.", "$ref": "#/definitions/mediaTypeList" }, "produces": { "description": "A list of MIME types the API can produce.", "$ref": "#/definitions/mediaTypeList" }, "paths": { "$ref": "#/definitions/paths" }, "definitions": { "$ref": "#/definitions/definitions" }, "parameters": { "$ref": "#/definitions/parameterDefinitions" }, "responses": { "$ref": "#/definitions/responseDefinitions" }, "security": { "$ref": "#/definitions/security" }, "securityDefinitions": { "$ref": "#/definitions/securityDefinitions" }, "tags": { "type": "array", "items": { "$ref": "#/definitions/tag" }, "uniqueItems": true }, "externalDocs": { "$ref": "#/definitions/externalDocs" } }, "definitions": { "info": { "type": "object", "description": "General information about the API.", "required": ["version", "title"], "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "title": { "type": "string", "description": "A unique and precise title of the API." }, "version": { "type": "string", "description": "A semantic version number of the API." }, "description": { "type": "string", "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." }, "termsOfService": { "type": "string", "description": "The terms of service for the API." }, "contact": { "$ref": "#/definitions/contact" }, "license": { "$ref": "#/definitions/license" } } }, "contact": { "type": "object", "description": "Contact information for the owners of the API.", "additionalProperties": false, "properties": { "name": { "type": "string", "description": "The identifying name of the contact person/organization." }, "url": { "type": "string", "description": "The URL pointing to the contact information.", "format": "uri" }, "email": { "type": "string", "description": "The email address of the contact person/organization.", "format": "email" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "license": { "type": "object", "required": ["name"], "additionalProperties": false, "properties": { "name": { "type": "string", "description": "The name of the license type. It's encouraged to use an OSI compatible license." }, "url": { "type": "string", "description": "The URL pointing to the license.", "format": "uri" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "paths": { "type": "object", "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" }, "^/": { "$ref": "#/definitions/pathItem" } }, "additionalProperties": false }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#/definitions/schema" }, "description": "One or more JSON objects describing the schemas being consumed and produced by the API." }, "parameterDefinitions": { "type": "object", "additionalProperties": { "$ref": "#/definitions/parameter" }, "description": "One or more JSON representations for parameters" }, "responseDefinitions": { "type": "object", "additionalProperties": { "$ref": "#/definitions/response" }, "description": "One or more JSON representations for parameters" }, "externalDocs": { "type": "object", "additionalProperties": false, "description": "information about external documentation", "required": ["url"], "properties": { "description": { "type": "string" }, "url": { "type": "string", "format": "uri" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "examples": { "type": "object", "additionalProperties": true }, "mimeType": { "type": "string", "description": "The MIME type of the HTTP message." }, "operation": { "type": "object", "required": ["responses"], "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "tags": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, "summary": { "type": "string", "description": "A brief summary of the operation." }, "description": { "type": "string", "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." }, "externalDocs": { "$ref": "#/definitions/externalDocs" }, "operationId": { "type": "string", "description": "A unique identifier of the operation." }, "produces": { "description": "A list of MIME types the API can produce.", "$ref": "#/definitions/mediaTypeList" }, "consumes": { "description": "A list of MIME types the API can consume.", "$ref": "#/definitions/mediaTypeList" }, "parameters": { "$ref": "#/definitions/parametersList" }, "responses": { "$ref": "#/definitions/responses" }, "schemes": { "$ref": "#/definitions/schemesList" }, "deprecated": { "type": "boolean", "default": false }, "security": { "$ref": "#/definitions/security" } } }, "pathItem": { "type": "object", "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "$ref": { "type": "string" }, "get": { "$ref": "#/definitions/operation" }, "put": { "$ref": "#/definitions/operation" }, "post": { "$ref": "#/definitions/operation" }, "delete": { "$ref": "#/definitions/operation" }, "options": { "$ref": "#/definitions/operation" }, "head": { "$ref": "#/definitions/operation" }, "patch": { "$ref": "#/definitions/operation" }, "parameters": { "$ref": "#/definitions/parametersList" } } }, "responses": { "type": "object", "description": "Response objects names can either be any valid HTTP status code or 'default'.", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^([0-9]{3})$|^(default)$": { "$ref": "#/definitions/responseValue" }, "^x-": { "$ref": "#/definitions/vendorExtension" } }, "not": { "type": "object", "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } } }, "responseValue": { "oneOf": [ { "$ref": "#/definitions/response" }, { "$ref": "#/definitions/jsonReference" } ] }, "response": { "type": "object", "required": ["description"], "properties": { "description": { "type": "string" }, "schema": { "oneOf": [ { "$ref": "#/definitions/schema" }, { "$ref": "#/definitions/fileSchema" } ] }, "headers": { "$ref": "#/definitions/headers" }, "examples": { "$ref": "#/definitions/examples" } }, "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "headers": { "type": "object", "additionalProperties": { "$ref": "#/definitions/header" } }, "header": { "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "type": "string", "enum": ["string", "number", "integer", "boolean", "array"] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "vendorExtension": { "description": "Any property starting with x- is valid.", "additionalProperties": true, "additionalItems": true }, "bodyParameter": { "type": "object", "required": ["name", "in", "schema"], "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": ["body"] }, "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "schema": { "$ref": "#/definitions/schema" } }, "additionalProperties": false }, "headerParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": ["header"] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "type": { "type": "string", "enum": ["string", "number", "boolean", "integer", "array"] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "queryParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": ["query"] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "allowEmptyValue": { "type": "boolean", "default": false, "description": "allows sending a parameter by name only or with an empty value." }, "type": { "type": "string", "enum": ["string", "number", "boolean", "integer", "array"] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormatWithMulti" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "formDataParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": ["formData"] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "allowEmptyValue": { "type": "boolean", "default": false, "description": "allows sending a parameter by name only or with an empty value." }, "type": { "type": "string", "enum": ["string", "number", "boolean", "integer", "array", "file"] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormatWithMulti" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "pathParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "required": ["required"], "properties": { "required": { "type": "boolean", "enum": [true], "description": "Determines whether or not this parameter is required or optional." }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": ["path"] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "type": { "type": "string", "enum": ["string", "number", "boolean", "integer", "array"] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "nonBodyParameter": { "type": "object", "required": ["name", "in", "type"], "oneOf": [ { "$ref": "#/definitions/headerParameterSubSchema" }, { "$ref": "#/definitions/formDataParameterSubSchema" }, { "$ref": "#/definitions/queryParameterSubSchema" }, { "$ref": "#/definitions/pathParameterSubSchema" } ] }, "parameter": { "oneOf": [ { "$ref": "#/definitions/bodyParameter" }, { "$ref": "#/definitions/nonBodyParameter" } ] }, "schema": { "type": "object", "description": "A deterministic version of a JSON Schema object.", "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "$ref": { "type": "string" }, "format": { "type": "string" }, "title": { "$ref": "http://json-schema.org/draft-07/schema#/properties/title" }, "description": { "$ref": "http://json-schema.org/draft-07/schema#/properties/description" }, "default": { "$ref": "http://json-schema.org/draft-07/schema#/properties/default" }, "multipleOf": { "$ref": "http://json-schema.org/draft-07/schema#/properties/multipleOf" }, "maximum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/maximum" }, "exclusiveMaximum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMaximum" }, "minimum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/minimum" }, "exclusiveMinimum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMinimum" }, "maxLength": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger" }, "minLength": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "pattern": { "$ref": "http://json-schema.org/draft-07/schema#/properties/pattern" }, "maxItems": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger" }, "minItems": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "uniqueItems": { "$ref": "http://json-schema.org/draft-07/schema#/properties/uniqueItems" }, "maxProperties": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger" }, "minProperties": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "required": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/stringArray" }, "enum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/enum" }, "additionalProperties": { "anyOf": [ { "$ref": "#/definitions/schema" }, { "type": "boolean" } ], "default": {} }, "type": { "$ref": "http://json-schema.org/draft-07/schema#/properties/type" }, "items": { "anyOf": [ { "$ref": "#/definitions/schema" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/schema" } } ], "default": {} }, "allOf": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/schema" } }, "properties": { "type": "object", "additionalProperties": { "$ref": "#/definitions/schema" }, "default": {} }, "discriminator": { "type": "string" }, "readOnly": { "type": "boolean", "default": false }, "xml": { "$ref": "#/definitions/xml" }, "externalDocs": { "$ref": "#/definitions/externalDocs" }, "example": {} }, "additionalProperties": false }, "fileSchema": { "type": "object", "description": "A deterministic version of a JSON Schema object.", "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "required": ["type"], "properties": { "format": { "type": "string" }, "title": { "$ref": "http://json-schema.org/draft-07/schema#/properties/title" }, "description": { "$ref": "http://json-schema.org/draft-07/schema#/properties/description" }, "default": { "$ref": "http://json-schema.org/draft-07/schema#/properties/default" }, "required": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/stringArray" }, "type": { "type": "string", "enum": ["file"] }, "readOnly": { "type": "boolean", "default": false }, "externalDocs": { "$ref": "#/definitions/externalDocs" }, "example": {} }, "additionalProperties": false }, "primitivesItems": { "type": "object", "additionalProperties": false, "properties": { "type": { "type": "string", "enum": ["string", "number", "integer", "boolean", "array"] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "security": { "type": "array", "items": { "$ref": "#/definitions/securityRequirement" }, "uniqueItems": true }, "securityRequirement": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" }, "uniqueItems": true } }, "xml": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string" }, "namespace": { "type": "string" }, "prefix": { "type": "string" }, "attribute": { "type": "boolean", "default": false }, "wrapped": { "type": "boolean", "default": false } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "tag": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "externalDocs": { "$ref": "#/definitions/externalDocs" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "securityDefinitions": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/basicAuthenticationSecurity" }, { "$ref": "#/definitions/apiKeySecurity" }, { "$ref": "#/definitions/oauth2ImplicitSecurity" }, { "$ref": "#/definitions/oauth2PasswordSecurity" }, { "$ref": "#/definitions/oauth2ApplicationSecurity" }, { "$ref": "#/definitions/oauth2AccessCodeSecurity" } ] } }, "basicAuthenticationSecurity": { "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "type": "string", "enum": ["basic"] }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "apiKeySecurity": { "type": "object", "additionalProperties": false, "required": ["type", "name", "in"], "properties": { "type": { "type": "string", "enum": ["apiKey"] }, "name": { "type": "string" }, "in": { "type": "string", "enum": ["header", "query"] }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2ImplicitSecurity": { "type": "object", "additionalProperties": false, "required": ["type", "flow", "authorizationUrl"], "properties": { "type": { "type": "string", "enum": ["oauth2"] }, "flow": { "type": "string", "enum": ["implicit"] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "authorizationUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2PasswordSecurity": { "type": "object", "additionalProperties": false, "required": ["type", "flow", "tokenUrl"], "properties": { "type": { "type": "string", "enum": ["oauth2"] }, "flow": { "type": "string", "enum": ["password"] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "tokenUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2ApplicationSecurity": { "type": "object", "additionalProperties": false, "required": ["type", "flow", "tokenUrl"], "properties": { "type": { "type": "string", "enum": ["oauth2"] }, "flow": { "type": "string", "enum": ["application"] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "tokenUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2AccessCodeSecurity": { "type": "object", "additionalProperties": false, "required": ["type", "flow", "authorizationUrl", "tokenUrl"], "properties": { "type": { "type": "string", "enum": ["oauth2"] }, "flow": { "type": "string", "enum": ["accessCode"] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "authorizationUrl": { "type": "string", "format": "uri" }, "tokenUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2Scopes": { "type": "object", "additionalProperties": { "type": "string" } }, "mediaTypeList": { "type": "array", "items": { "$ref": "#/definitions/mimeType" }, "uniqueItems": true }, "parametersList": { "type": "array", "description": "The parameters needed to send a valid API call.", "additionalItems": false, "items": { "oneOf": [ { "$ref": "#/definitions/parameter" }, { "$ref": "#/definitions/jsonReference" } ] }, "uniqueItems": true }, "schemesList": { "type": "array", "description": "The transfer protocol of the API.", "items": { "type": "string", "enum": ["http", "https", "ws", "wss"] }, "uniqueItems": true }, "collectionFormat": { "type": "string", "enum": ["csv", "ssv", "tsv", "pipes"], "default": "csv" }, "collectionFormatWithMulti": { "type": "string", "enum": ["csv", "ssv", "tsv", "pipes", "multi"], "default": "csv" }, "title": { "$ref": "http://json-schema.org/draft-07/schema#/properties/title" }, "description": { "$ref": "http://json-schema.org/draft-07/schema#/properties/description" }, "default": { "$ref": "http://json-schema.org/draft-07/schema#/properties/default" }, "multipleOf": { "$ref": "http://json-schema.org/draft-07/schema#/properties/multipleOf" }, "maximum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/maximum" }, "exclusiveMaximum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMaximum" }, "minimum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/minimum" }, "exclusiveMinimum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMinimum" }, "maxLength": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger" }, "minLength": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "pattern": { "$ref": "http://json-schema.org/draft-07/schema#/properties/pattern" }, "maxItems": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger" }, "minItems": { "$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0" }, "uniqueItems": { "$ref": "http://json-schema.org/draft-07/schema#/properties/uniqueItems" }, "enum": { "$ref": "http://json-schema.org/draft-07/schema#/properties/enum" }, "jsonReference": { "type": "object", "required": ["$ref"], "additionalProperties": false, "properties": { "$ref": { "type": "string" } } } } }, "tests": [ { "description": "empty object is invalid", "data": {}, "valid": false }, { "description": "minimal valid object", "data": { "swagger": "2.0", "info": { "title": "sample api definition", "version": "0.1" }, "paths": {} }, "valid": true } ] } ] ================================================ FILE: spec/tests/issues/861_empty_propertynames.json ================================================ [ { "description": "propertyNames with empty schema (#861)", "schema": { "properties": { "foo": {"type": "string"} }, "propertyNames": {} }, "tests": [ { "description": "valid", "data": {"foo": "bar"}, "valid": true }, { "description": "invalid", "data": {"foo": 1}, "valid": false } ] } ] ================================================ FILE: spec/tests/issues/87_$_property.json ================================================ [ { "description": "$ in properties (#87)", "schema": { "properties": { "$": {"type": "string"} } }, "tests": [ { "description": "valid", "data": {"$": "foo"}, "valid": true } ] } ] ================================================ FILE: spec/tests/issues/94_dependencies_fail.json ================================================ [ { "description": "second dependency is not checked (#94)", "schema": { "dependencies": { "bar": ["baz"], "foo": ["bar"] } }, "tests": [ { "description": "object with only foo is invalid (bar is missing)", "data": {"foo": 1}, "valid": false }, { "description": "object with foo and bar is invalid (baz is missing)", "data": {"foo": 1, "bar": 2}, "valid": false }, { "description": "object with foo, bar and baz is valid", "data": {"foo": 1, "bar": 2, "baz": 3}, "valid": true } ] }, { "description": "second dependency is checked when order is changed", "schema": { "dependencies": { "foo": ["bar"], "bar": ["baz"] } }, "tests": [ { "description": "object with only foo is invalid (bar is missing)", "data": {"foo": 1}, "valid": false }, { "description": "object with foo and bar is invalid (baz is missing)", "data": {"foo": 1, "bar": 2}, "valid": false }, { "description": "object with foo, bar and baz is valid", "data": {"foo": 1, "bar": 2, "baz": 3}, "valid": true } ] } ] ================================================ FILE: spec/tests/rules/allOf.json ================================================ [ { "description": "allOf with one empty schema", "schema": { "allOf": [{}] }, "tests": [ { "description": "any data is valid", "data": 1, "valid": true } ] }, { "description": "allOf with two empty schemas", "schema": { "allOf": [{}, {}] }, "tests": [ { "description": "any data is valid", "data": 1, "valid": true } ] }, { "description": "allOf with two schemas, the first is empty", "schema": { "allOf": [{}, {"type": "number"}] }, "tests": [ { "description": "number is valid", "data": 1, "valid": true }, { "description": "string is invalid", "data": "foo", "valid": false } ] }, { "description": "allOf with two schemas, the second is empty", "schema": { "allOf": [{"type": "number"}, {}] }, "tests": [ { "description": "number is valid", "data": 1, "valid": true }, { "description": "string is invalid", "data": "foo", "valid": false } ] } ] ================================================ FILE: spec/tests/rules/anyOf.json ================================================ [ { "description": "anyOf with one of schemas empty", "schema": { "anyOf": [{"type": "number"}, {}] }, "tests": [ { "description": "string is valid", "data": "foo", "valid": true }, { "description": "number is valid", "data": 123, "valid": true } ] } ] ================================================ FILE: spec/tests/rules/comment.json ================================================ [ { "description": "$comment keyword", "schema": { "$comment": "test" }, "tests": [ { "description": "any value is valid", "data": 1, "valid": true } ] }, { "description": "$comment keyword in subschemas", "schema": { "type": "object", "properties": { "foo": { "$comment": "test" } } }, "tests": [ { "description": "empty object is valid", "data": {}, "valid": true }, { "description": "any value of property foo is valid object is valid", "data": { "foo": 1 }, "valid": true } ] } ] ================================================ FILE: spec/tests/rules/dependencies.json ================================================ [ { "description": "dependencies keyword with empty array", "schema": { "dependencies": { "foo": [] } }, "tests": [ { "description": "object with property is valid", "data": {"foo": 1}, "valid": true }, { "description": "empty object is valid", "data": {}, "valid": true }, { "description": "non-object is valid", "data": 1, "valid": true } ] } ] ================================================ FILE: spec/tests/rules/format.json ================================================ [ { "description": "allowed unknown format is valid", "schema": { "format": "allowedUnknown" }, "tests": [ { "description": "any string is valid", "data": "any value", "valid": true } ] } ] ================================================ FILE: spec/tests/rules/if.json ================================================ [ { "description": "if/then keyword validation", "schema": { "if": {"minimum": 10}, "then": {"multipleOf": 2} }, "tests": [ { "description": ">= 10 and even is valid", "data": 12, "valid": true }, { "description": ">= 10 and odd is invalid", "data": 11, "valid": false }, { "description": "< 10 is valid", "data": 9, "valid": true } ] }, { "description": "if/then/else keyword validation", "schema": { "if": {"maximum": 10}, "then": {"multipleOf": 2}, "else": {"multipleOf": 5} }, "tests": [ { "description": "<=10 and even is valid", "data": 8, "valid": true }, { "description": "<=10 and odd is invalid", "data": 7, "valid": false }, { "description": ">10 and mulitple of 5 is valid", "data": 15, "valid": true }, { "description": ">10 and not mulitple of 5 is invalid", "data": 17, "valid": false } ] }, { "description": "if keyword with id in sibling subschema", "schema": { "$id": "http://example.com/base_if", "if": { "$id": "http://example.com/if", "minimum": 10 }, "then": {"$ref": "#/definitions/def"}, "definitions": { "def": {"multipleOf": 2} } }, "tests": [ { "description": ">= 10 and even is valid", "data": 12, "valid": true }, { "description": ">= 10 and odd is invalid", "data": 11, "valid": false }, { "description": "< 10 is valid", "data": 9, "valid": true } ] }, { "description": "then/else without if should be ignored", "schema": { "then": {"multipleOf": 2}, "else": {"multipleOf": 5} }, "tests": [ { "description": "even is valid", "data": 8, "valid": true }, { "description": "odd is valid", "data": 7, "valid": true }, { "description": "mulitple of 5 is valid", "data": 15, "valid": true }, { "description": "not mulitple of 5 is valid", "data": 17, "valid": true } ] }, { "description": "if without then/else should be ignored", "schema": { "if": {"maximum": 10} }, "tests": [ { "description": "<=10 is valid", "data": 8, "valid": true }, { "description": ">10 is valid", "data": 15, "valid": true } ] } ] ================================================ FILE: spec/tests/rules/items.json ================================================ [ { "description": "items with empty schema", "schema": { "items": [{}], "additionalItems": {"type": "string"} }, "tests": [ { "description": "array with second string is valid", "data": [1, "a"], "valid": true }, { "description": "array with second number is invalid", "data": [1, 2], "valid": false } ] }, { "description": "items with subitems", "schema": { "definitions": { "child": { "type": "array", "additionalItems": false, "items": [{"$ref": "#/definitions/sub-child"}, {"$ref": "#/definitions/sub-child"}] }, "sub-child": { "type": "object", "properties": {"foo": {}}, "required": ["foo"], "additionalProperties": false } }, "type": "array", "additionalItems": false, "items": [ {"$ref": "#/definitions/child"}, {"$ref": "#/definitions/child"}, {"$ref": "#/definitions/child"} ] }, "tests": [ { "description": "valid items", "data": [ [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}] ], "valid": true }, { "description": "too many children", "data": [ [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}] ], "valid": false }, { "description": "too many sub-children", "data": [ [{"foo": null}, {"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}] ], "valid": false }, { "description": "wrong child", "data": [{"foo": null}, [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}]], "valid": false }, { "description": "wrong sub-child", "data": [ [{"bar": null}, {"foo": null}], [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}] ], "valid": false }, { "description": "fewer children is valid", "data": [[{"foo": null}], [{"foo": null}]], "valid": true } ] }, { "description": "deeply nested items", "schema": { "type": "array", "items": { "type": "array", "items": { "type": "array", "items": { "type": "array", "items": { "type": "number" } } } } }, "tests": [ { "description": "valid nested array", "data": [[[[1]], [[2], [3]]], [[[4], [5], [6]]]], "valid": true }, { "description": "nested array with invalid type", "data": [[[["1"]], [[2], [3]]], [[[4], [5], [6]]]], "valid": false }, { "description": "not deep enough", "data": [ [[1], [2], [3]], [[4], [5], [6]] ], "valid": false } ] } ] ================================================ FILE: spec/tests/rules/oneOf.json ================================================ [ { "description": "oneOf with one of schemas empty", "schema": { "oneOf": [{"type": "number"}, {}] }, "tests": [ { "description": "string is valid", "data": "foo", "valid": true }, { "description": "number is invalid", "data": 123, "valid": false } ] }, { "description": "oneOf with required", "schema": { "type": "object", "oneOf": [{"required": ["foo", "bar"]}, {"required": ["foo", "baz"]}] }, "tests": [ { "description": "object with foo and bar is valid", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "object with foo and baz is valid", "data": {"foo": 1, "baz": 3}, "valid": true }, { "description": "object with foo, bar and baz is invalid", "data": {"foo": 1, "bar": 2, "baz": 3}, "valid": false } ] }, { "description": "oneOf with required with 20+ properties", "schema": { "type": "object", "oneOf": [ {"required": ["foo", "bar"]}, { "required": [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" ] } ] }, "tests": [ { "description": "object with foo and bar is valid", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "object with a, b, c, ... properties is valid", "data": { "a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0, "h": 0, "i": 0, "j": 0, "k": 0, "l": 0, "m": 0, "n": 0, "o": 0, "p": 0, "q": 0, "r": 0, "s": 0, "t": 0, "u": 0, "v": 0, "w": 0, "x": 0, "y": 0, "z": 0 }, "valid": true }, { "description": "object with foo, bar and a, b, c ... is invalid", "data": { "a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0, "h": 0, "i": 0, "j": 0, "k": 0, "l": 0, "m": 0, "n": 0, "o": 0, "p": 0, "q": 0, "r": 0, "s": 0, "t": 0, "u": 0, "v": 0, "w": 0, "x": 0, "y": 0, "z": 0, "foo": 1, "bar": 2 }, "valid": false } ] } ] ================================================ FILE: spec/tests/rules/required.json ================================================ [ { "description": "required keyword with empty array", "schema": { "required": [] }, "tests": [ { "description": "object with property is valid", "data": {"foo": 1}, "valid": true }, { "description": "empty object is valid", "data": {}, "valid": true }, { "description": "non-object is valid", "data": 1, "valid": true } ] } ] ================================================ FILE: spec/tests/rules/type.json ================================================ [ { "description": "type as array with one item", "schema": { "type": ["string"] }, "tests": [ { "description": "string is valid", "data": "foo", "valid": true }, { "description": "number is invalid", "data": 123, "valid": false } ] }, { "description": "type: array or object", "schema": { "type": ["array", "object"] }, "tests": [ { "description": "array is valid", "data": [1, 2, 3], "valid": true }, { "description": "object is valid", "data": {"foo": 123}, "valid": true }, { "description": "number is invalid", "data": 123, "valid": false }, { "description": "string is invalid", "data": "foo", "valid": false }, { "description": "null is invalid", "data": null, "valid": false } ] }, { "description": "type: array, object or null", "schema": { "type": ["array", "object", "null"] }, "tests": [ { "description": "array is valid", "data": [1, 2, 3], "valid": true }, { "description": "object is valid", "data": {"foo": 123}, "valid": true }, { "description": "null is valid", "data": null, "valid": true }, { "description": "number is invalid", "data": 123, "valid": false }, { "description": "string is invalid", "data": "foo", "valid": false } ] } ] ================================================ FILE: spec/tests/rules/uniqueItems.json ================================================ [ { "description": "uniqueItems with algorithm using hash", "schema": { "items": {"type": "string"}, "uniqueItems": true }, "tests": [ { "description": "array of unique strings is valid", "data": ["foo", "bar", "baz"], "valid": true }, { "description": "array of unique items with strings that are properties of hash is valid", "data": ["toString", "foo"], "valid": true }, { "description": "array of non-unique strings is invalid", "data": ["foo", "bar", "bar"], "valid": false }, { "description": "array with non-strings is invalid", "data": ["1", 2], "valid": false } ] }, { "description": "uniqueItems with multiple types when the list of types includes array", "schema": { "items": {"type": ["array", "string"]}, "uniqueItems": true }, "tests": [ { "description": "array of unique items is valid", "data": [[1], [2], "foo"], "valid": true }, { "description": "array of non-unique items is invalid", "data": [[1], [1], "foo"], "valid": false }, { "description": "array with incorrect type is invalid", "data": [{}, 1, 2], "valid": false } ] }, { "description": "uniqueItems with multiple types when the list of types includes object", "schema": { "items": {"type": ["object", "string"]}, "uniqueItems": true }, "tests": [ { "description": "array of unique items is valid", "data": [{"a": 1}, {"b": 2}, "foo"], "valid": true }, { "description": "array of non-unique items is invalid", "data": [{"a": 1}, {"a": 1}, "foo"], "valid": false }, { "description": "array with incorrect type is invalid", "data": [[], 1, 2], "valid": false } ] }, { "description": "uniqueItems with multiple types when all types are scalar", "schema": { "items": {"type": ["number", "string", "boolean", "null"]}, "uniqueItems": true }, "tests": [ { "description": "array of unique items is valid (string/number)", "data": ["1", 1, 2], "valid": true }, { "description": "array of unique items is valid (string/boolean)", "data": ["true", true, false], "valid": true }, { "description": "array of unique items is valid (string/null)", "data": ["null", null, 0], "valid": true }, { "description": "array of non-unique items is invalid", "data": [1, 1, 2], "valid": false }, { "description": "array with incorrect type is invalid", "data": [[], 1, 2], "valid": false } ] } ] ================================================ FILE: spec/tests/schemas/advanced.json ================================================ [ { "description": "advanced schema from z-schema benchmark (https://github.com/zaggino/z-schema)", "schema": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "/": {"$ref": "#/definitions/entry"} }, "patternProperties": { "^(/[^/]+)+$": {"$ref": "#/definitions/entry"} }, "additionalProperties": false, "required": ["/"], "definitions": { "entry": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "schema for an fstab entry", "type": "object", "required": ["storage"], "properties": { "storage": { "type": "object", "oneOf": [ {"$ref": "#/definitions/entry/definitions/diskDevice"}, {"$ref": "#/definitions/entry/definitions/diskUUID"}, {"$ref": "#/definitions/entry/definitions/nfs"}, {"$ref": "#/definitions/entry/definitions/tmpfs"} ] }, "fstype": { "enum": ["ext3", "ext4", "btrfs"] }, "options": { "type": "array", "minItems": 1, "items": {"type": "string"}, "uniqueItems": true }, "readonly": {"type": "boolean"} }, "definitions": { "diskDevice": { "properties": { "type": {"enum": ["disk"]}, "device": { "type": "string", "pattern": "^/dev/[^/]+(/[^/]+)*$" } }, "required": ["type", "device"], "additionalProperties": false }, "diskUUID": { "properties": { "type": {"enum": ["disk"]}, "label": { "type": "string", "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" } }, "required": ["type", "label"], "additionalProperties": false }, "nfs": { "properties": { "type": {"enum": ["nfs"]}, "remotePath": { "type": "string", "pattern": "^(/[^/]+)+$" }, "server": { "type": "string", "anyOf": [{"format": "hostname"}, {"format": "ipv4"}, {"format": "ipv6"}] } }, "required": ["type", "server", "remotePath"], "additionalProperties": false }, "tmpfs": { "properties": { "type": {"enum": ["tmpfs"]}, "sizeInMB": { "type": "integer", "minimum": 16, "maximum": 512 } }, "required": ["type", "sizeInMB"], "additionalProperties": false } } } } }, "tests": [ { "description": "valid object from z-schema benchmark", "data": { "/": { "storage": { "type": "disk", "device": "/dev/sda1" }, "fstype": "btrfs", "readonly": true }, "/var": { "storage": { "type": "disk", "label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f" }, "fstype": "ext4", "options": ["nosuid"] }, "/tmp": { "storage": { "type": "tmpfs", "sizeInMB": 64 } }, "/var/www": { "storage": { "type": "nfs", "server": "my.nfs.server", "remotePath": "/exports/mypath" } } }, "valid": true }, { "description": "not object", "data": 1, "valid": false }, { "description": "root only is valid", "data": { "/": { "storage": { "type": "disk", "device": "/dev/sda1" }, "fstype": "btrfs", "readonly": true } }, "valid": true }, { "description": "missing root entry", "data": { "no root/": { "storage": { "type": "disk", "device": "/dev/sda1" }, "fstype": "btrfs", "readonly": true } }, "valid": false }, { "description": "invalid entry key", "data": { "/": { "storage": { "type": "disk", "device": "/dev/sda1" }, "fstype": "btrfs", "readonly": true }, "invalid/var": { "storage": { "type": "disk", "label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f" }, "fstype": "ext4", "options": ["nosuid"] } }, "valid": false }, { "description": "missing storage in entry", "data": { "/": { "fstype": "btrfs", "readonly": true } }, "valid": false }, { "description": "missing storage type", "data": { "/": { "storage": { "device": "/dev/sda1" }, "fstype": "btrfs", "readonly": true } }, "valid": false }, { "description": "storage type should be a string", "data": { "/": { "storage": { "type": null, "device": "/dev/sda1" }, "fstype": "btrfs", "readonly": true } }, "valid": false }, { "description": "storage device should match pattern", "data": { "/": { "storage": { "type": null, "device": "invalid/dev/sda1" }, "fstype": "btrfs", "readonly": true } }, "valid": false } ] } ] ================================================ FILE: spec/tests/schemas/basic.json ================================================ [ { "description": "basic schema from z-schema benchmark (https://github.com/zaggino/z-schema)", "schema": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Product set", "type": "array", "items": { "title": "Product", "type": "object", "properties": { "id": { "description": "The unique identifier for a product", "type": "number" }, "name": { "type": "string" }, "price": { "type": "number", "exclusiveMinimum": 0 }, "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true }, "dimensions": { "type": "object", "properties": { "length": {"type": "number"}, "width": {"type": "number"}, "height": {"type": "number"} }, "required": ["length", "width", "height"] }, "warehouseLocation": { "description": "Coordinates of the warehouse with the product" } }, "required": ["id", "name", "price"] } }, "tests": [ { "description": "valid array from z-schema benchmark", "data": [ { "id": 2, "name": "An ice sculpture", "price": 12.5, "tags": ["cold", "ice"], "dimensions": { "length": 7.0, "width": 12.0, "height": 9.5 }, "warehouseLocation": { "latitude": -78.75, "longitude": 20.4 } }, { "id": 3, "name": "A blue mouse", "price": 25.5, "dimensions": { "length": 3.1, "width": 1.0, "height": 1.0 }, "warehouseLocation": { "latitude": 54.4, "longitude": -32.7 } } ], "valid": true }, { "description": "not array", "data": 1, "valid": false }, { "description": "array of not onjects", "data": [1, 2, 3], "valid": false }, { "description": "missing required properties", "data": [{}], "valid": false }, { "description": "required property of wrong type", "data": [{"id": 1, "name": "product", "price": "not valid"}], "valid": false }, { "description": "smallest valid product", "data": [{"id": 1, "name": "product", "price": 100}], "valid": true }, { "description": "tags should be array", "data": [{"tags": {}, "id": 1, "name": "product", "price": 100}], "valid": false }, { "description": "dimensions should be object", "data": [{"dimensions": [], "id": 1, "name": "product", "price": 100}], "valid": false }, { "description": "valid product with tag", "data": [{"tags": ["product"], "id": 1, "name": "product", "price": 100}], "valid": true }, { "description": "dimensions miss required properties", "data": [ { "dimensions": {}, "tags": ["product"], "id": 1, "name": "product", "price": 100 } ], "valid": false }, { "description": "valid product with tag and dimensions", "data": [ { "dimensions": {"length": 7, "width": 12, "height": 9.5}, "tags": ["product"], "id": 1, "name": "product", "price": 100 } ], "valid": true } ] } ] ================================================ FILE: spec/tests/schemas/complex.json ================================================ [ { "description": "complex schema from jsck benchmark (https://github.com/pandastrike/jsck)", "schema": { "type": "array", "items": {"$ref": "#transaction"}, "minItems": 1, "definitions": { "base58": { "$id": "#base58", "type": "string", "pattern": "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$" }, "hex": { "$id": "#hex", "type": "string", "pattern": "^[0123456789A-Fa-f]+$" }, "tx_id": { "$id": "#tx_id", "allOf": [ {"$ref": "#hex"}, { "minLength": 64, "maxLength": 64 } ] }, "address": { "$id": "#address", "allOf": [ {"$ref": "#base58"}, { "minLength": 34, "maxLength": 34 } ] }, "signature": { "$id": "#signature", "allOf": [ {"$ref": "#hex"}, { "minLength": 128, "maxLength": 128 } ] }, "transaction": { "$id": "#transaction", "additionalProperties": false, "required": ["metadata", "hash", "inputs", "outputs"], "properties": { "metadata": { "type": "object", "required": ["amount", "fee"], "properties": { "amount": { "type": "integer" }, "fee": { "type": "integer", "multipleOf": 10000 }, "status": { "type": "string", "enum": ["unsigned", "unconfirmed", "confirmed", "invalid"] }, "confirmations": { "type": "integer", "minimum": 0 }, "block_time": { "type": "integer" } } }, "version": { "type": "integer" }, "lock_time": { "type": "integer" }, "hash": {"$ref": "#tx_id"}, "inputs": { "type": "array", "items": {"$ref": "#input"}, "minItems": 1 }, "outputs": { "type": "array", "items": {"$ref": "#output"}, "minItems": 1 } } }, "input": { "$id": "#input", "type": "object", "additionalProperties": false, "required": ["index", "output", "script_sig"], "properties": { "index": { "type": "integer", "minimum": 0 }, "output": {"$ref": "#output"}, "sig_hash": {"$ref": "#hex"}, "script_sig": {"$ref": "#hex"}, "signatures": { "type": "object", "description": "A dictionary of signatures. Keys represent keypair names", "minProperties": 1, "maxProperties": 3, "additionalProperties": {"$ref": "#signature"} } } }, "output": { "$id": "#output", "type": "object", "additionalProperties": false, "required": ["hash", "index", "value", "script"], "properties": { "hash": {"$ref": "#tx_id"}, "index": { "type": "integer", "minimum": 0 }, "value": { "type": "integer" }, "script": { "type": "object", "properties": { "type": { "type": "string", "enum": ["standard", "p2sh"] }, "asm": { "type": "string" } } }, "address": {"$ref": "#address"}, "metadata": { "type": "object", "dependencies": { "wallet_path": ["public_seeds"] }, "properties": { "wallet_path": { "type": "string" }, "public_seeds": { "type": "object", "minProperties": 1, "maxProperties": 3, "additionalProperties": { "anyOf": [{"$ref": "#base58"}, {"$ref": "#hex"}] } } } } } } } }, "tests": [ { "description": "valid array from jsck benchmark", "data": [ { "metadata": { "amount": 38043749285, "fee": 20000, "status": "confirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "signatures": { "primary": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a7", "cosigner": "a2ad5ebf16dadf9d357ef2867cb9b1de682b336db000b6e0012200ebda7c8802f7c5ea2afd97439840a191c756be6528521b214487d5fc79796eb00122064037" }, "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change", "wallet_path": "m/44/0/1/356", "public_seeds": { "primary": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "cosigner": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" } } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] }, { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] }, { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] } ], "valid": true }, { "description": "not array", "data": 1, "valid": false } ] } ] ================================================ FILE: spec/tests/schemas/complex2.json ================================================ [ { "description": "complex schema from jsck benchmark without IDs in definitions", "schema": { "type": "array", "items": {"$ref": "#/definitions/transaction"}, "minItems": 1, "definitions": { "base58": { "type": "string", "pattern": "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$" }, "hex": { "type": "string", "pattern": "^[0123456789A-Fa-f]+$" }, "tx_id": { "allOf": [ {"$ref": "#/definitions/hex"}, { "minLength": 64, "maxLength": 64 } ] }, "address": { "allOf": [ {"$ref": "#/definitions/base58"}, { "minLength": 34, "maxLength": 34 } ] }, "signature": { "allOf": [ {"$ref": "#/definitions/hex"}, { "minLength": 128, "maxLength": 128 } ] }, "transaction": { "additionalProperties": false, "required": ["metadata", "hash", "inputs", "outputs"], "properties": { "metadata": { "type": "object", "required": ["amount", "fee"], "properties": { "amount": { "type": "integer" }, "fee": { "type": "integer", "multipleOf": 10000 }, "status": { "type": "string", "enum": ["unsigned", "unconfirmed", "confirmed", "invalid"] }, "confirmations": { "type": "integer", "minimum": 0 }, "block_time": { "type": "integer" } } }, "version": { "type": "integer" }, "lock_time": { "type": "integer" }, "hash": {"$ref": "#/definitions/tx_id"}, "inputs": { "type": "array", "items": {"$ref": "#/definitions/input"}, "minItems": 1 }, "outputs": { "type": "array", "items": {"$ref": "#/definitions/output"}, "minItems": 1 } } }, "input": { "type": "object", "additionalProperties": false, "required": ["index", "output", "script_sig"], "properties": { "index": { "type": "integer", "minimum": 0 }, "output": {"$ref": "#/definitions/output"}, "sig_hash": {"$ref": "#/definitions/hex"}, "script_sig": {"$ref": "#/definitions/hex"}, "signatures": { "type": "object", "description": "A dictionary of signatures. Keys represent keypair names", "minProperties": 1, "maxProperties": 3, "additionalProperties": {"$ref": "#/definitions/signature"} } } }, "output": { "type": "object", "additionalProperties": false, "required": ["hash", "index", "value", "script"], "properties": { "hash": {"$ref": "#/definitions/tx_id"}, "index": { "type": "integer", "minimum": 0 }, "value": { "type": "integer" }, "script": { "type": "object", "properties": { "type": { "type": "string", "enum": ["standard", "p2sh"] }, "asm": { "type": "string" } } }, "address": {"$ref": "#/definitions/address"}, "metadata": { "type": "object", "dependencies": { "wallet_path": ["public_seeds"] }, "properties": { "wallet_path": { "type": "string" }, "public_seeds": { "type": "object", "minProperties": 1, "maxProperties": 3, "additionalProperties": { "anyOf": [{"$ref": "#/definitions/base58"}, {"$ref": "#/definitions/hex"}] } } } } } } } }, "tests": [ { "description": "valid array from jsck benchmark", "data": [ { "metadata": { "amount": 38043749285, "fee": 20000, "status": "confirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "signatures": { "primary": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a7", "cosigner": "a2ad5ebf16dadf9d357ef2867cb9b1de682b336db000b6e0012200ebda7c8802f7c5ea2afd97439840a191c756be6528521b214487d5fc79796eb00122064037" }, "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change", "wallet_path": "m/44/0/1/356", "public_seeds": { "primary": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "cosigner": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" } } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] }, { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] }, { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] } ], "valid": true }, { "description": "not array", "data": 1, "valid": false }, { "description": "one valid item", "data": [ { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] } ], "valid": true }, { "description": "one invalid item", "data": [ { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "$_is_invalid", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] } ], "valid": false } ] } ] ================================================ FILE: spec/tests/schemas/complex3.json ================================================ [ { "description": "complex schema from jsck benchmark (https://github.com/pandastrike/jsck)", "schema": { "$id": "http://example.com/complex3.json", "type": "array", "items": {"$ref": "#transaction"}, "minItems": 1, "definitions": { "base58": { "$id": "#base58", "type": "string", "pattern": "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$" }, "hex": { "$id": "#hex", "type": "string", "pattern": "^[0123456789A-Fa-f]+$" }, "tx_id": { "$id": "#tx_id", "allOf": [ {"$ref": "#hex"}, { "minLength": 64, "maxLength": 64 } ] }, "address": { "$id": "#address", "allOf": [ {"$ref": "#base58"}, { "minLength": 34, "maxLength": 34 } ] }, "signature": { "$id": "#signature", "allOf": [ {"$ref": "#hex"}, { "minLength": 128, "maxLength": 128 } ] }, "transaction": { "$id": "#transaction", "additionalProperties": false, "required": ["metadata", "hash", "inputs", "outputs"], "properties": { "metadata": { "type": "object", "required": ["amount", "fee"], "properties": { "amount": { "type": "integer" }, "fee": { "type": "integer", "multipleOf": 10000 }, "status": { "type": "string", "enum": ["unsigned", "unconfirmed", "confirmed", "invalid"] }, "confirmations": { "type": "integer", "minimum": 0 }, "block_time": { "type": "integer" } } }, "version": { "type": "integer" }, "lock_time": { "type": "integer" }, "hash": {"$ref": "#tx_id"}, "inputs": { "type": "array", "items": {"$ref": "#input"}, "minItems": 1 }, "outputs": { "type": "array", "items": {"$ref": "#output"}, "minItems": 1 } } }, "input": { "$id": "#input", "type": "object", "additionalProperties": false, "required": ["index", "output", "script_sig"], "properties": { "index": { "type": "integer", "minimum": 0 }, "output": {"$ref": "#output"}, "sig_hash": {"$ref": "#hex"}, "script_sig": {"$ref": "#hex"}, "signatures": { "type": "object", "description": "A dictionary of signatures. Keys represent keypair names", "minProperties": 1, "maxProperties": 3, "additionalProperties": {"$ref": "#signature"} } } }, "output": { "$id": "#output", "type": "object", "additionalProperties": false, "required": ["hash", "index", "value", "script"], "properties": { "hash": {"$ref": "#tx_id"}, "index": { "type": "integer", "minimum": 0 }, "value": { "type": "integer" }, "script": { "type": "object", "properties": { "type": { "type": "string", "enum": ["standard", "p2sh"] }, "asm": { "type": "string" } } }, "address": {"$ref": "#address"}, "metadata": { "type": "object", "dependencies": { "wallet_path": ["public_seeds"] }, "properties": { "wallet_path": { "type": "string" }, "public_seeds": { "type": "object", "minProperties": 1, "maxProperties": 3, "additionalProperties": { "anyOf": [{"$ref": "#base58"}, {"$ref": "#hex"}] } } } } } } } }, "tests": [ { "description": "valid array from jsck benchmark", "data": [ { "metadata": { "amount": 38043749285, "fee": 20000, "status": "confirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "signatures": { "primary": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a7", "cosigner": "a2ad5ebf16dadf9d357ef2867cb9b1de682b336db000b6e0012200ebda7c8802f7c5ea2afd97439840a191c756be6528521b214487d5fc79796eb00122064037" }, "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change", "wallet_path": "m/44/0/1/356", "public_seeds": { "primary": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "cosigner": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" } } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] }, { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] }, { "metadata": { "amount": 38043749285, "fee": 20000, "status": "unconfirmed", "confirmations": 73, "block_time": 1415993584376 }, "version": 1, "lock_time": 0, "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "inputs": [ { "index": 0, "script_sig": "3046022100be69797cf5d784412b1258256eb657c191a04893479dfa2ae5c7f2088c7adbe0022100e6b000bd633b286ed1b9bc7682fe753d9fdad61fbe5da2a6e9444198e33a670f01", "output": { "hash": "6b040cd7a4676b5c7b11f144e73c1958c177fcd79e934f6be8ce02c8cd12546d", "index": 1, "value": 38043749285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" } } } ], "outputs": [ { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 0, "value": 38042249285, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 7d4e6d55e1dffb0df85f509343451d170d147551 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "1CRZRBwfuwUaVSPJtd6DBuezbm7XPBHLa1", "metadata": { "type": "change" } }, { "hash": "60c1f1a3160042152114e2bba45600a5045711c3a8a458016248acec59653471", "index": 1, "value": 1500000, "script": { "type": "standard", "asm": "OP_DUP OP_HASH160 3bc576e6960a9d45201ba5087e39224d0a05a079 OP_EQUALVERIFY OP_CHECKSIG" }, "address": "16T3RPZLmxtXQCgWi1S8kef5Ca6jqXhoeT" } ] } ], "valid": true }, { "description": "not array", "data": 1, "valid": false } ] } ] ================================================ FILE: spec/tests/schemas/cosmicrealms.json ================================================ [ { "description": "schema from cosmicrealms benchmark", "schema": { "name": "test", "type": "object", "additionalProperties": false, "required": [ "fullName", "age", "zip", "married", "dozen", "dozenOrBakersDozen", "favoriteEvenNumber", "topThreeFavoriteColors", "favoriteSingleDigitWholeNumbers", "favoriteFiveLetterWord", "emailAddresses", "ipAddresses" ], "properties": { "fullName": {"type": "string"}, "age": {"type": "integer", "minimum": 0}, "optionalItem": {"type": "string"}, "state": {"type": "string"}, "city": {"type": "string"}, "zip": {"type": "integer", "minimum": 0, "maximum": 99999}, "married": {"type": "boolean"}, "dozen": {"type": "integer", "minimum": 12, "maximum": 12}, "dozenOrBakersDozen": {"type": "integer", "minimum": 12, "maximum": 13}, "favoriteEvenNumber": {"type": "integer", "multipleOf": 2}, "topThreeFavoriteColors": { "type": "array", "minItems": 3, "maxItems": 3, "uniqueItems": true, "items": {"type": "string"} }, "favoriteSingleDigitWholeNumbers": { "type": "array", "minItems": 1, "maxItems": 10, "uniqueItems": true, "items": {"type": "integer", "minimum": 0, "maximum": 9} }, "favoriteFiveLetterWord": { "type": "string", "minLength": 5, "maxLength": 5 }, "emailAddresses": { "type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "format": "email"} }, "ipAddresses": { "type": "array", "uniqueItems": true, "items": {"type": "string", "format": "ipv4"} } } }, "tests": [ { "description": "valid data from cosmicrealms benchmark", "data": { "fullName": "John Smith", "state": "CA", "city": "Los Angeles", "favoriteFiveLetterWord": "hello", "emailAddresses": [ "NRorsfCYtvB5bKAf1jZMu1GAJzAhhg5lEvh@inTqnn.net", "6tjWtYxjaan2Ivm5QZVhKxImKawRCA6gcqtMEwV1@bB01pCtIBY0F.org", "j68UnHfrHiKwpAm8iYokoMuRTpWUj8bfxspusNFK@COoWeMZL.edu", "qlnrIsYSWCGUQW6f8HL@UBOqUYQQzugVL.uk" ], "dozen": 12, "dozenOrBakersDozen": 13, "favoriteEvenNumber": 24, "married": true, "age": 17, "zip": 65794, "topThreeFavoriteColors": ["blue", "black", "yellow"], "favoriteSingleDigitWholeNumbers": [2, 1, 3, 9], "ipAddresses": ["225.234.40.3", "96.216.243.54", "18.126.145.83", "196.17.191.239"] }, "valid": true }, { "description": "invalid data", "data": { "state": null, "city": 90912, "zip": [null], "married": "married", "dozen": 90912, "dozenOrBakersDozen": null, "favoriteEvenNumber": -1294145, "emailAddresses": [], "topThreeFavoriteColors": [ null, null, 0.7925170068027211, 1.2478632478632479, 1.173913043478261, 0.4472049689440994 ], "favoriteSingleDigitWholeNumbers": [], "favoriteFiveLetterWord": "more than five letters", "ipAddresses": [ "55.335.74.758", "191.266.92.805", "193.388.390.250", "269.375.318.49", "120.268.59.140" ] }, "valid": false } ] } ] ================================================ FILE: spec/tests/schemas/medium.json ================================================ [ { "description": "medium schema from jsck benchmark (https://github.com/pandastrike/jsck)", "schema": { "description": "A moderately complex schema with some nesting and value constraints", "type": "object", "additionalProperties": false, "required": ["api_server", "transport", "storage", "chain"], "properties": { "api_server": { "description": "Settings for the HTTP API server", "type": "object", "additionalProperties": false, "required": ["url", "host", "port"], "properties": { "url": { "type": "string", "format": "uri" }, "host": { "type": "string" }, "port": { "type": "integer", "minimum": 1000 } } }, "transport": { "description": "Settings for the Redis tranport", "additionalProperties": false, "required": ["server"], "properties": { "server": { "type": "string" }, "options": { "type": "object" }, "queues": { "properties": { "blocking_timeout": { "type": "integer", "minimum": 0 } } } } }, "storage": { "description": "Settings for the PostgreSQL storage", "required": ["server", "database", "user"], "properties": { "server": { "type": "string" }, "database": { "type": "string" }, "user": { "type": "string" }, "options": { "type": "object" } } }, "chain": { "description": "Settings for the Chain.com client", "required": ["api_key_id", "api_key_secret"], "properties": { "api_key_id": { "type": "string" }, "api_key_secret": { "type": "string" } } } } }, "tests": [ { "description": "valid object from jsck benchmark", "data": { "api_server": { "url": "http://example.com:8998", "host": "example.com", "port": 8998 }, "transport": { "server": "127.0.0.1:6381", "queues": { "blocking_timeout": 0 } }, "storage": { "server": "127.0.0.1:5432", "database": "thingy-test", "user": "thingy-test", "password": "password" }, "chain": { "api_key_id": "cafebabe", "api_key_secret": "babecafe" } }, "valid": true }, { "description": "not object", "data": 1, "valid": false } ] } ] ================================================ FILE: spec/tsconfig.json ================================================ { "extends": "..", "include": ["."], "compilerOptions": { "types": ["node", "mocha"], "noImplicitAny": false } } ================================================ FILE: spec/types/async-validate.spec.ts ================================================ import type {AnySchemaObject, SchemaObject, AsyncSchema} from "../.." import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() interface Foo { foo: number } describe("$async validation and type guards", () => { const ajv = new _Ajv({strictTypes: false}) describe("$async: undefined", () => { it("should have result type boolean 1", () => { const validate = ajv.compile({ type: "object", properties: {foo: {type: "number"}}, }) const data: unknown = {foo: 1} let result: boolean if ((result = validate(data))) { data.foo.should.equal(1) } result.should.equal(true) }) it("should have result type boolean 2", () => { const schema: SchemaObject = { type: "object", properties: {foo: {type: "number"}}, } const validate = ajv.compile(schema) const data: unknown = {foo: 1} let result: boolean if ((result = validate(data))) { data.foo.should.equal(1) } result.should.equal(true) }) it("should have result type boolean 3", () => { const schema: AnySchemaObject = { type: "object", properties: {foo: {type: "number"}}, } const validate = ajv.compile(schema) const data: unknown = {foo: 1} let result: boolean if ((result = validate(data))) { data.foo.should.equal(1) } result.should.equal(true) }) }) describe("$async: false", () => { it("should have result type boolean 1", () => { const validate = ajv.compile({$async: false}) const result: boolean = validate({}) should.exist(result) }) it("should have result type boolean 2", () => { const schema: SchemaObject = {$async: false} const validate = ajv.compile(schema) const result: boolean = validate({}) should.exist(result) }) it("should have result type boolean 3", () => { const schema: AnySchemaObject = {$async: false} const validate = ajv.compile(schema) const result: boolean = validate({}) should.exist(result) }) }) describe("$async: true", () => { it("should have result type promise 1", async () => { const validate = ajv.compile({ $async: true, type: "object", properties: {foo: {type: "number"}}, }) const result: Promise = validate({foo: 1}) await result.then((data) => data.should.exist) }) it("should have result type promise 2", async () => { const schema: AsyncSchema = { $async: true, type: "object", properties: {foo: {type: "number"}}, } const validate = ajv.compile(schema) const result: Promise = validate({foo: 1}) await result.then((data) => data.foo.should.equal(1)) }) }) describe("$async: boolean", () => { it("should have result type boolean | promise 1", async () => { const schema = { $async: true, type: "object", properties: {foo: {type: "number"}}, } const validate = ajv.compile(schema) const data: unknown = {foo: 1} let result: boolean | Promise if ((result = validate(data))) { if (result instanceof Promise) { await result.then((_data) => _data.foo.should.equal(1)) } else { should.fail() } } else { should.fail() } }) it("should have result type boolean | promise 2", async () => { const schema = {$async: false} const validate = ajv.compile(schema) const result = validate({}) if (typeof result === "boolean") { should.exist(result) } else { await result.then((data) => data.should.exist) } }) }) describe("$async: unknown", () => { const schema: Record = { type: "object", properties: {foo: {type: "number"}}, } const validate = ajv.compile(schema) it("should have result type boolean", () => { const data = {foo: 1} let result: boolean if ((result = validate(data))) { data.foo.should.equal(1) } result.should.equal(true) }) }) describe("schema: any", () => { const schema: any = {} const validate = ajv.compile(schema) it("should have result type boolean | promise", () => { const result: boolean = validate({}) result.should.equal(true) }) }) }) ================================================ FILE: spec/types/error-parameters.spec.ts ================================================ import {DefinedError} from "../.." import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() describe("error object parameters type", () => { const ajv = new _Ajv({allErrors: true}) it("should be determined by the keyword", () => { const validate = ajv.compile({type: "number", minimum: 0, multipleOf: 2}) const valid = validate(-1) valid.should.equal(false) const errs = validate.errors if (errs) { errs.length.should.equal(2) for (const err of errs as DefinedError[]) { switch (err.keyword) { case "minimum": err.params.limit.should.equal(0) err.params.comparison.should.equal(">=") break case "multipleOf": err.params.multipleOf.should.equal(2) break default: should.fail() } } } }) }) ================================================ FILE: spec/types/json-schema.spec.ts ================================================ import _Ajv from "../ajv" import type {JSONSchemaType} from "../.." import type {SchemaObject} from "../.." import chai from "../chai" const should = chai.should() interface MyData { foo: string bar?: number // "boo" should be present if "bar" is present baz: { quux: "quux" [x: string]: string } boo?: true tuple?: readonly [number, string] arr: {id: number}[] map: {[K in string]?: number} notBoo?: string // should not be present if "boo" is present negativeIfBoo?: number // should be negative if "boo" is present } const arrSchema: JSONSchemaType = { type: "array", items: { type: "object", properties: { id: { type: "integer", }, }, additionalProperties: false, required: ["id"], }, uniqueItems: true, } as const const mySchema: JSONSchemaType & { definitions: { baz: JSONSchemaType tuple: JSONSchemaType } } = { type: "object", definitions: { baz: { // schema type is checked here ... type: "object", properties: { quux: {type: "string", const: "quux"}, }, patternProperties: { abc: {type: "string"}, }, additionalProperties: false, required: [], }, tuple: { // ... and here ... type: "array", items: [{type: "number"}, {type: "string"}], minItems: 2, additionalItems: false, nullable: true, }, }, dependencies: { bar: ["boo"], boo: { not: {required: ["notBoo"]}, // optional properties can be cheched in "required" in PartialSchema required: ["negativeIfBoo"], properties: { // partial properties can be used in partial schemas negativeIfBoo: {type: "number", nullable: true, exclusiveMaximum: 0}, }, }, }, properties: { foo: {type: "string"}, bar: {type: "number", nullable: true}, baz: {$ref: "#/definitions/baz"}, // ... but it does not check type here, ... boo: { type: "boolean", nullable: true, enum: [true, null], }, tuple: {$ref: "#/definitions/tuple"}, // ... nor here. arr: arrSchema, // ... The alternative is to define it externally - here it checks type map: { type: "object", required: [], additionalProperties: {type: "number"}, }, notBoo: {type: "string", nullable: true}, negativeIfBoo: {type: "number", nullable: true}, }, additionalProperties: false, required: ["foo", "baz", "arr", "map"], // any other property added here won't typecheck } as const type MyUnionData = {a: boolean} | string | number const myUnionSchema: JSONSchemaType = { anyOf: [ { type: "object", properties: { a: {type: "boolean"}, }, required: ["a"], }, { type: ["string", "number"], // can specify properties for either type minimum: 0, minLength: 1, }, ], } as const // because of the current definition, you can do this nested recusion const myNestedUnionSchema: JSONSchemaType = { anyOf: [ { oneOf: [ { type: "object", properties: { a: {type: "boolean"}, }, required: ["a"], }, { type: "string", }, ], }, { type: "number", }, ], } as const // allowing union types necessarily allows this to pass :/ const emptyType: JSONSchemaType = { type: [], } as const type MyEnumRecord = Record<"a" | "b" | "c" | "d", number | undefined> describe("JSONSchemaType type and validation as a type guard", () => { const ajv = new _Ajv({allowUnionTypes: true}) const validData: unknown = { foo: "foo", bar: 1, baz: { quux: "quux", abc: "abc", }, boo: true, arr: [{id: 1}, {id: 2}], tuple: [1, "abc"], map: { a: 1, b: 2, }, negativeIfBoo: -1, } describe("schema has type JSONSchemaType", () => { it("should prove the type of validated data", () => { const validate = ajv.compile(mySchema) if (validate(validData)) { validData.foo.should.equal("foo") } should.not.exist(validate.errors) if (ajv.validate(mySchema, validData)) { validData.foo.should.equal("foo") } should.not.exist(ajv.errors) }) }) const validUnionData: unknown = { a: true, } describe("schema has type JSONSchemaType", () => { it("should prove the type of validated data", () => { const validate = ajv.compile(myUnionSchema) if (validate(validUnionData)) { if (typeof validUnionData === "string") { should.fail("not a string") } else if (typeof validUnionData === "number") { should.fail("not a number") } else { validUnionData.a.should.equal(true) } } else { should.fail("is valid") } should.not.exist(validate.errors) if (ajv.validate(myUnionSchema, validUnionData)) { if (typeof validUnionData === "string") { should.fail("not a string") } else if (typeof validUnionData === "number") { should.fail("not a number") } else { validUnionData.a.should.equal(true) } } else { should.fail("is valid") } should.not.exist(ajv.errors) }) it("should prove the type of validated nested data", () => { const validate = ajv.compile(myNestedUnionSchema) if (validate(validUnionData)) { if (typeof validUnionData === "string") { should.fail("not a string") } else if (typeof validUnionData === "number") { should.fail("not a number") } else { validUnionData.a.should.equal(true) } } else { should.fail("is valid") } should.not.exist(validate.errors) if (ajv.validate(myNestedUnionSchema, validUnionData)) { if (typeof validUnionData === "string") { should.fail("not a string") } else if (typeof validUnionData === "number") { should.fail("not a number") } else { validUnionData.a.should.equal(true) } } else { should.fail("is valid") } should.not.exist(ajv.errors) }) it("should fail for invalid unions", () => { // @ts-expect-error extra type const extraSchema: JSONSchemaType = { type: ["string", "number", "boolean"], } as const // @ts-expect-error extra properties const extraProps: JSONSchemaType = { type: ["string", "boolean"], maximum: 5, // number property } as const // eslint-disable-next-line no-void void [extraSchema, extraProps] }) }) describe("schema has type SchemaObject", () => { it("should prove the type of validated data", () => { const schema = mySchema as SchemaObject const validate = ajv.compile(schema) if (validate(validData)) { validData.foo.should.equal("foo") } should.not.exist(validate.errors) if (ajv.validate(schema, validData)) { validData.foo.should.equal("foo") } should.not.exist(ajv.errors) }) }) describe("schema should be simple for record types", () => { it("typechecks a valid type without required", () => { const myEnumRecordSchema: JSONSchemaType = { type: "object", propertyNames: {enum: ["a", "b", "c", "d"]}, additionalProperty: {type: "number"}, } // eslint-disable-next-line no-void void myEnumRecordSchema }) it("requires required for non-optional types", () => { // @ts-expect-error missing required const requiredSchema: JSONSchemaType<{a: number}> = { type: "object", } // eslint-disable-next-line no-void void requiredSchema }) it("doesn't require required for optional types", () => { const optionalSchema: JSONSchemaType<{a?: number}> = { type: "object", } // eslint-disable-next-line no-void void optionalSchema }) it("won't accept nullable for non-null types", () => { // @ts-expect-error can't set nullable const nonNullSchema: JSONSchemaType<{a: number}> = { type: "object", properties: { a: { type: "number", nullable: true, }, }, required: [], } // eslint-disable-next-line no-void void nonNullSchema }) }) describe("schema works for primitives", () => { it("allows partial boolean sub schemas", () => { // this schema doesn't have much meaning, but it wasn't allowed before const trueSchema: JSONSchemaType = { type: "boolean", not: {const: false}, } as const // eslint-disable-next-line no-void void trueSchema }) it("validates simple null", () => { const nullSchema: JSONSchemaType = { type: "null", nullable: true, const: null, enum: [null], } as const const validate = ajv.compile(nullSchema) // eslint-disable-next-line @typescript-eslint/no-unused-expressions validate(null).should.be.true }) }) }) // eslint-disable-next-line no-void void emptyType ================================================ FILE: spec/types/jtd-schema.spec.ts ================================================ /* eslint-disable @typescript-eslint/no-empty-interface,no-void,@typescript-eslint/ban-types */ import _Ajv from "../ajv_jtd" import type {JTDSchemaType, SomeJTDSchemaType, JTDDataType} from "../../dist/jtd" import chai from "../chai" const should = chai.should() /** type is true if T is identically E */ type TypeEquality = [T] extends [E] ? ([E] extends [T] ? true : false) : false interface A { type: "a" a: number } interface B { type: "b" b?: string } interface C { type: "c" } type MyData = A | B type Missing = A | C interface LinkedList { val: number next?: LinkedList } const mySchema: JTDSchemaType = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, b: {optionalProperties: {b: {type: "string"}}}, }, } const missingSchema: JTDSchemaType = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, c: {properties: {}}, }, } describe("JTDSchemaType", () => { it("validation should prove the data type", () => { const ajv = new _Ajv() const validate = ajv.compile(mySchema) const validData: unknown = {type: "a", a: 1} if (validate(validData) && validData.type === "a") { validData.a.should.equal(1) } should.not.exist(validate.errors) if (ajv.validate(mySchema, validData) && validData.type === "a") { validData.a.should.equal(1) } should.not.exist(ajv.errors) }) it("parser should return correct data type", () => { const ajv = new _Ajv() const parse = ajv.compileParser(mySchema) const validJson = '{"type": "a", "a": 1}' const data = parse(validJson) if (data !== undefined && data.type === "a") { data.a.should.equal(1) } should.not.exist(parse.message) }) it("serializer should only accept correct data type", () => { const ajv = new _Ajv() const serialize = ajv.compileSerializer(mySchema) const validData = {type: "a" as const, a: 1} serialize(validData).should.equal('{"type":"a","a":1}') const invalidData = {type: "a" as const, b: "test"} // @ts-expect-error serialize(invalidData) }) it("validation should prove the data type for missingSchema", () => { const ajv = new _Ajv() const validate = ajv.compile(missingSchema) const validData: unknown = {type: "c"} if (validate(validData)) { validData.type.should.equal("c") } should.not.exist(validate.errors) if (ajv.validate(missingSchema, validData)) { validData.type.should.equal("c") } should.not.exist(validate.errors) }) it("should typecheck number schemas", () => { const numf: JTDSchemaType = {type: "float64"} const numi: JTDSchemaType = {type: "int32"} // @ts-expect-error const numl: JTDSchemaType = {type: "int64"} // number literals don't work // @ts-expect-error const nums: JTDSchemaType<1 | 2 | 3> = {type: "int32"} const numNull: JTDSchemaType = {type: "int32", nullable: true} // @ts-expect-error const numNotNull: JTDSchemaType = {type: "float32"} void [numf, numi, numl, nums, numNull, numNotNull] }) it("should typecheck boolean schemas", () => { const bool: JTDSchemaType = {type: "boolean"} // boolean literals don't // @ts-expect-error const boolTrue: JTDSchemaType = {type: "boolean"} const boolNull: JTDSchemaType = {type: "boolean", nullable: true} void [bool, boolTrue, boolNull] }) it("should typecheck string schemas", () => { const str: JTDSchemaType = {type: "string"} const time: JTDSchemaType = {type: "timestamp"} const strNull: JTDSchemaType = {type: "string", nullable: true} void [str, time, strNull] }) it("should typecheck dates", () => { const time: JTDSchemaType = {type: "timestamp"} const timeNull: JTDSchemaType = {type: "timestamp", nullable: true} void [time, timeNull] }) it("should typecheck enumeration schemas", () => { const enumerate: JTDSchemaType<"a" | "b"> = {enum: ["a", "b"]} // don't need to specify everything const enumerateMissing: JTDSchemaType<"a" | "b" | "c"> = {enum: ["a", "b"]} // must all be string literals // @ts-expect-error const enumerateNumber: JTDSchemaType<"a" | "b" | 5> = {enum: ["a", "b"]} // can't overgeneralize in schema // @ts-expect-error const enumerateString: JTDSchemaType<"a" | "b"> = {type: "string"} const enumerateNull: JTDSchemaType<"a" | "b" | null> = {enum: ["a", "b"], nullable: true} void [enumerate, enumerateMissing, enumerateNumber, enumerateString, enumerateNull] }) it("should typecheck elements schemas", () => { const elements: JTDSchemaType = {elements: {type: "float64"}} const readonlyElements: JTDSchemaType = {elements: {type: "float64"}} // tuples don't work // @ts-expect-error const tupleHomo: JTDSchemaType<[number, number]> = {elements: {type: "float64"}} // @ts-expect-error const tupleHeteroNum: JTDSchemaType<[number, string]> = { elements: {type: "float64"}, } // @ts-expect-error const tupleHeteroString: JTDSchemaType<[number, string]> = { elements: {type: "string"}, } const elemNull: JTDSchemaType = {elements: {type: "float64"}, nullable: true} // can typecheck an array of unions const unionElem: TypeEquality, never> = false // can't typecheck a union of arrays const elemUnion: TypeEquality, never> = true void [ elements, readonlyElements, tupleHomo, tupleHeteroNum, tupleHeteroString, elemNull, unionElem, elemUnion, ] }) it("should typecheck values schemas", () => { const values: JTDSchemaType> = {values: {type: "float64"}} const readonlyValues: JTDSchemaType>> = { values: {type: "float64"}, } // values must be a whole mapping // @ts-expect-error const valuesDefined: JTDSchemaType<{prop: number}> = {values: {type: "float64"}} const valuesNull: JTDSchemaType | null> = { values: {type: "float64"}, nullable: true, } // can typecheck a values of unions const unionValues: TypeEquality>, never> = false // can't typecheck a union of values const valuesUnion: TypeEquality< JTDSchemaType | Record>, never > = true void [values, readonlyValues, valuesDefined, valuesNull, unionValues, valuesUnion] }) it("should typecheck properties schemas", () => { const properties: JTDSchemaType<{a: number; b: string}> = { properties: {a: {type: "float64"}, b: {type: "string"}}, } const optionalProperties: JTDSchemaType<{a?: number; b?: string}> = { optionalProperties: {a: {type: "float64"}, b: {type: "string"}}, additionalProperties: false, } const mixedProperties: JTDSchemaType<{a: number; b?: string}> = { properties: {a: {type: "float64"}}, optionalProperties: {b: {type: "string"}}, additionalProperties: true, } const fewerProperties: JTDSchemaType<{a: number; b: string}> = { // @ts-expect-error properties: {a: {type: "float64"}}, } const propertiesNull: JTDSchemaType<{a: number; b: string} | null> = { properties: {a: {type: "float64"}, b: {type: "string"}}, nullable: true, } void [properties, optionalProperties, mixedProperties, fewerProperties, propertiesNull] }) it("should typecheck discriminator schemas", () => { const union: JTDSchemaType = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, b: { optionalProperties: {b: {type: "string"}}, }, }, } // can't mess up, e.g. value type isn't a union const unionDuplicate: JTDSchemaType = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, // @ts-expect-error b: {properties: {a: {type: "float64"}}}, }, } // must specify everything const unionMissing: JTDSchemaType = { discriminator: "type", // @ts-expect-error mapping: { a: {properties: {a: {type: "float64"}}}, }, } // can use any valid discrinimator type Mult = JTDSchemaType<(A & {typ: "alpha"}) | (B & {typ: "beta"})> const multOne: Mult = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}, typ: {enum: ["alpha"]}}}, b: { properties: {typ: {enum: ["beta"]}}, optionalProperties: {b: {type: "string"}}, }, }, } const multTwo: Mult = { discriminator: "typ", mapping: { alpha: {properties: {a: {type: "float64"}, type: {enum: ["a"]}}}, beta: { properties: {type: {enum: ["b"]}}, optionalProperties: {b: {type: "string"}}, }, }, } const unionNull: JTDSchemaType = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, b: { optionalProperties: {b: {type: "string"}}, }, }, nullable: true, } // properties must have common key const noCommon: TypeEquality< JTDSchemaType<{key1: "a"; a: number} | {key2: "b"; b: string}>, never > = true void [union, unionDuplicate, unionMissing, multOne, multTwo, unionNull, noCommon] }) it("should typecheck empty schemas", () => { const empty: JTDSchemaType = {} // unknown can be null const emptyUnknown: JTDSchemaType = {nullable: true} // somewhat unintuitively, it can still have nullable: false even though it can be null const falseUnknown: JTDSchemaType = {nullable: false} // can only use empty for empty and null // @ts-expect-error const emptyButFull: JTDSchemaType<{a: string}> = {} const emptyMeta: JTDSchemaType = {metadata: {}} // constant null representable as nullable empty object const emptyNull: TypeEquality, never> = true void [empty, emptyUnknown, falseUnknown, emptyButFull, emptyMeta, emptyNull] }) it("should typecheck empty records", () => { // empty record variants const emptyPro: JTDSchemaType<{}> = {properties: {}} const emptyOpt: JTDSchemaType<{}> = {optionalProperties: {}} const emptyBoth: JTDSchemaType<{}> = {properties: {}, optionalProperties: {}} const emptyRecord: JTDSchemaType> = {properties: {}} const notNullable: JTDSchemaType<{}> = {properties: {}, nullable: false} // can't be null // @ts-expect-error const nullable: JTDSchemaType<{}> = {properties: {}, nullable: true} const emptyNullUnion: JTDSchemaType = {properties: {}, nullable: true} const emptyNullRecord: JTDSchemaType> = { properties: {}, nullable: true, } void [ emptyPro, emptyOpt, emptyBoth, emptyRecord, notNullable, nullable, emptyNullUnion, emptyNullRecord, ] }) it("should typecheck ref schemas", () => { const refs: JTDSchemaType = { definitions: { num: {type: "float64"}, }, elements: {ref: "num"}, } const missingDef: JTDSchemaType = { // @ts-expect-error definitions: {}, elements: {ref: "num"}, } const missingType: JTDSchemaType = { definitions: {}, // @ts-expect-error elements: {ref: "num"}, } const nullRefs: JTDSchemaType = { definitions: { num: {type: "float64"}, }, ref: "num", nullable: true, } const refsNullOne: JTDSchemaType = { definitions: { num: {type: "float64", nullable: true}, }, ref: "num", } const refsNullTwo: JTDSchemaType = { definitions: { num: {type: "float64", nullable: true}, }, ref: "num", nullable: true, } void [refs, missingDef, missingType, nullRefs, refsNullOne, refsNullTwo] }) it("should typecheck metadata schemas", () => { const meta: JTDSchemaType = {type: "float32", metadata: {key: "val"}} const emptyMeta: JTDSchemaType = {metadata: {key: "val"}} const unknownMeta: JTDSchemaType = {nullable: true, metadata: {key: "val"}} void [meta, emptyMeta, unknownMeta] }) }) describe("JTDDataType", () => { it("validation should prove the data type", () => { const ajv = new _Ajv() const mySchema1 = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, b: {optionalProperties: {b: {type: "string"}}}, }, } as const const validate = ajv.compile(mySchema1) const validData: unknown = {type: "a", a: 1} if (validate(validData) && validData.type === "a") { validData.a.should.equal(1) } should.not.exist(validate.errors) if (ajv.validate(mySchema1, validData) && validData.type === "a") { validData.a.should.equal(1) } should.not.exist(ajv.errors) }) it("should typecheck number schemas", () => { const numSchema = {type: "float64"} as const const num: TypeEquality, number> = true void [num] }) it("should typecheck boolean schemas", () => { const booleanSchema = {type: "boolean"} as const const bool: TypeEquality, boolean> = true void [bool] }) it("should typecheck string schemas", () => { const strSchema = {type: "string"} as const const str: TypeEquality, string> = true void [str] }) it("should typecheck timestamp schemas", () => { const timeSchema = {type: "timestamp"} as const const time: TypeEquality, string | Date> = true void [time] }) it("should typecheck enum schemas", () => { const enumSchema = {enum: ["a", "b"]} as const const enumerated: TypeEquality, "a" | "b"> = true // if you forget const on an enum it will error const enumStringSchema = {enum: ["a", "b"]} const enumString: TypeEquality, never> = true // also if not a string const enumNumSchema = {enum: [3]} as const const enumNum: TypeEquality, never> = true void [enumerated, enumString, enumNum] }) it("should typecheck elements schemas", () => { const elementsSchema = {elements: {type: "float64"}} as const const elem: TypeEquality, number[]> = true void [elem] }) it("should typecheck properties schemas", () => { const bothPropsSchema = { properties: {a: {type: "float64"}}, optionalProperties: {b: {type: "string"}}, } as const const both: TypeEquality, {a: number; b?: string}> = true const reqPropsSchema = {properties: {a: {type: "float64"}}} as const const req: TypeEquality, {a: number}> = true const optPropsSchema = {optionalProperties: {b: {type: "string"}}} as const const opt: TypeEquality, {b?: string}> = true const noAddSchema = { optionalProperties: {b: {type: "string"}}, additionalProperties: false, } as const const noAdd: TypeEquality, {b?: string}> = true const addSchema = { optionalProperties: {b: {type: "string"}}, additionalProperties: true, } as const const add: TypeEquality< JTDDataType, {b?: string; [key: string]: unknown} > = true const addVal: JTDDataType = {b: "b", additional: 6} void [both, req, opt, noAdd, add, addVal] }) it("should typecheck values schemas", () => { const valuesSchema = {values: {type: "float64"}} as const const values: TypeEquality, Record> = true void [values] }) it("should typecheck discriminator schemas", () => { const discriminatorSchema = { discriminator: "type", mapping: { a: {properties: {a: {type: "float64"}}}, b: {optionalProperties: {b: {type: "string"}}}, }, } as const const disc: TypeEquality, A | B> = true void [disc] }) it("should typecheck ref schemas", () => { const refSchema = { definitions: {num: {type: "float64", nullable: true}}, ref: "num", nullable: true, } as const const ref: TypeEquality, number | null> = true // works for recursive schemas const llSchema = { definitions: { node: { properties: {val: {type: "float64"}}, optionalProperties: {next: {ref: "node"}}, }, }, ref: "node", } as const const list: TypeEquality, LinkedList> = true void [ref, list] }) it("should typecheck empty schemas", () => { const emptySchema = {metadata: {}} as const const empty: TypeEquality, unknown> = true void [empty] }) }) describe("SomeJTDSchemaType", () => { it("should allow setting unknowns", () => { // This test is basically here to assert that we should be using `{}` in // SomeJTDSchemaType const schema: SomeJTDSchemaType = {} void [schema] }) }) ================================================ FILE: tsconfig.json ================================================ { "extends": "@ajv-validator/config", "include": ["lib"], "compilerOptions": { "outDir": "dist", "lib": ["ES2018", "DOM"], "types": ["node"], "allowJs": true, "target": "ES2018", "resolveJsonModule": true } }