Uplift json schema validator library 12/138512/1
authoradheli.tavares <adheli.tavares@est.tech>
Fri, 19 Jul 2024 11:14:20 +0000 (12:14 +0100)
committeradheli.tavares <adheli.tavares@est.tech>
Fri, 19 Jul 2024 13:02:08 +0000 (14:02 +0100)
Issue-ID: POLICY-5084
Change-Id: Ic0413a07d052b62cf81fb9f128ecca892d76aa73
Signed-off-by: adheli.tavares <adheli.tavares@est.tech>
14 files changed:
gson/src/test/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapterTest.java
pom.xml
utils-test/src/main/java/org/onap/policy/common/utils/gson/GsonTestUtils.java
utils/pom.xml
utils/src/main/java/org/onap/policy/common/utils/coder/CoderException.java
utils/src/main/java/org/onap/policy/common/utils/coder/CoderRuntimeException.java [new file with mode: 0644]
utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoder.java
utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoderObject.java
utils/src/main/java/org/onap/policy/common/utils/coder/StandardValCoder.java
utils/src/main/java/org/onap/policy/common/utils/coder/StandardYamlCoder.java
utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderInstantAsMillisTest.java
utils/src/test/java/org/onap/policy/common/utils/coder/StandardValCoderTest.java
utils/src/test/resources/org/onap/policy/common/utils/coder/test.schema.json
utils/src/test/resources/org/onap/policy/common/utils/coder/valid.json

index a334c30..0e4e958 100644 (file)
@@ -33,7 +33,7 @@ import lombok.ToString;
 import org.junit.jupiter.api.Test;
 
 class ZoneOffsetTypeAdapterTest {
-    private static Gson gson =
+    private static final Gson gson =
             new GsonBuilder().registerTypeAdapter(ZoneOffset.class, new ZoneOffsetTypeAdapter()).create();
     private static final String TEST_ZONE = "+05:00";
 
diff --git a/pom.xml b/pom.xml
index 4e8f2b4..ae6d1b0 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 </project>
index da62569..1d131ae 100644 (file)
@@ -49,6 +49,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Utilities used to test encoding and decoding of Policy objects.
  */
+@Getter
 @AllArgsConstructor(access = AccessLevel.PROTECTED)
 public class GsonTestUtils {
 
@@ -67,7 +68,6 @@ public class GsonTestUtils {
     /**
      * Used to encode and decode an object via gson.
      */
-    @Getter
     private Gson gson;
 
     /**
index 32ec4a8..699ed40 100644 (file)
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>com.worldturner.medeia</groupId>
-            <artifactId>medeia-validator-gson</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.worldturner.medeia</groupId>
-            <artifactId>medeia-validator-core</artifactId>
+            <groupId>net.jimblackler.jsonschemafriend</groupId>
+            <artifactId>core</artifactId>
         </dependency>
         <dependency>
             <groupId>commons-net</groupId>
index 60e8573..8390d17 100644 (file)
@@ -1,8 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
- * ONAP PAP
- * ================================================================================
  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.policy.common.utils.coder;
 
+import java.io.Serial;
+
 /**
  * Exceptions generated by coders.
  */
 public class CoderException extends Exception {
+
+    @Serial
     private static final long serialVersionUID = 1L;
 
     public CoderException() {
diff --git a/utils/src/main/java/org/onap/policy/common/utils/coder/CoderRuntimeException.java b/utils/src/main/java/org/onap/policy/common/utils/coder/CoderRuntimeException.java
new file mode 100644 (file)
index 0000000..0ffd607
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.coder;
+
+import java.io.Serial;
+
+/**
+ * Exceptions generated by coders.
+ */
+public class CoderRuntimeException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public CoderRuntimeException(Throwable cause) {
+        super(cause);
+    }
+
+}
index d6135af..834a850 100644 (file)
@@ -3,6 +3,7 @@
  * ONAP
  * ================================================================================
  * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -179,7 +180,6 @@ public class StandardCoder implements Coder {
     public <T> T decode(String json, Class<T> clazz) throws CoderException {
         try {
             return fromJson(json, clazz);
-
         } catch (RuntimeException e) {
             throw new CoderException(e);
         }
@@ -378,7 +378,7 @@ public class StandardCoder implements Coder {
         /**
          * Used to read/write a JsonElement.
          */
-        private static TypeAdapter<JsonElement> elementAdapter = new Gson().getAdapter(JsonElement.class);
+        private static final TypeAdapter<JsonElement> elementAdapter = new Gson().getAdapter(JsonElement.class);
 
         @Override
         public void write(JsonWriter out, StandardCoderObject value) throws IOException {
index 55f7f9d..5402f37 100644 (file)
@@ -3,6 +3,7 @@
  * ONAP
  * ================================================================================
  * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +22,7 @@
 package org.onap.policy.common.utils.coder;
 
 import com.google.gson.JsonElement;
+import java.io.Serial;
 import java.io.Serializable;
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
@@ -33,6 +35,8 @@ import lombok.Getter;
  */
 @AllArgsConstructor(access = AccessLevel.PROTECTED)
 public class StandardCoderObject implements Serializable {
+
+    @Serial
     private static final long serialVersionUID = 1L;
 
     /**
@@ -56,9 +60,8 @@ public class StandardCoderObject implements Serializable {
      * Gets a field's value from this object, traversing the object hierarchy.
      *
      * @param fields field hierarchy. These may be strings, identifying fields within the
-     *        object, or Integers, identifying an index within an array
-     * @return the field value or {@code null} if the field does not exist or is not a
-     *         primitive
+     *               object, or Integers, identifying an index within an array
+     * @return the field value or {@code null} if the field does not exist or is not a primitive
      */
     public String getString(Object... fields) {
 
@@ -87,9 +90,8 @@ public class StandardCoderObject implements Serializable {
      * Gets an item from an object.
      *
      * @param element object from which to extract the item
-     * @param field name of the field from which to extract the item
-     * @return the item, or {@code null} if the element is not an object or if the field
-     *         does not exist
+     * @param field   name of the field from which to extract the item
+     * @return the item, or {@code null} if the element is not an object or if the field does not exist
      */
     protected JsonElement getFieldFromObject(JsonElement element, String field) {
         if (!element.isJsonObject()) {
@@ -103,9 +105,8 @@ public class StandardCoderObject implements Serializable {
      * Gets an item from an array.
      *
      * @param element array from which to extract the item
-     * @param index index of the item to extract
-     * @return the item, or {@code null} if the element is not an array or if the index is
-     *         out of bounds
+     * @param index   index of the item to extract
+     * @return the item, or {@code null} if the element is not an array or if the index is out of bounds
      */
     protected JsonElement getItemFromArray(JsonElement element, int index) {
         if (index < 0) {
index 4deeba1..408ae81 100644 (file)
@@ -1,6 +1,7 @@
 /*--
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.policy.common.utils.coder;
 
-import com.worldturner.medeia.api.JsonSchemaVersion;
-import com.worldturner.medeia.api.SchemaSource;
-import com.worldturner.medeia.api.StringSchemaSource;
-import com.worldturner.medeia.api.ValidationFailedException;
-import com.worldturner.medeia.api.gson.MedeiaGsonApi;
-import com.worldturner.medeia.schema.validation.SchemaValidator;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.io.Writer;
 import lombok.NonNull;
 import lombok.ToString;
+import net.jimblackler.jsonschemafriend.GenerationException;
+import net.jimblackler.jsonschemafriend.Schema;
+import net.jimblackler.jsonschemafriend.SchemaStore;
+import net.jimblackler.jsonschemafriend.ValidationException;
+import net.jimblackler.jsonschemafriend.Validator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,31 +42,29 @@ import org.slf4j.LoggerFactory;
 @ToString
 public class StandardValCoder extends StandardCoder {
 
-    // The medeia-validator library integrates better than
-    // other libraries considered with GSON, and therefore
-    // the StandardCoder.
-
     private static final Logger logger = LoggerFactory.getLogger(StandardValCoder.class);
 
-    private final MedeiaGsonApi validatorApi = new MedeiaGsonApi();
-    private final SchemaValidator validator;
+    private final Schema schema;
 
     /**
      * StandardCoder with validation.
      */
-    public StandardValCoder(@NonNull String jsonSchema, @NonNull String name) {
-        SchemaSource schemaSource = new StringSchemaSource(jsonSchema, JsonSchemaVersion.DRAFT07, null, name);
-        this.validator = validatorApi.loadSchema(schemaSource);
+    public StandardValCoder(@NonNull String jsonSchema) {
+        try {
+            SchemaStore store = new SchemaStore();
+            this.schema = store.loadSchemaJson(jsonSchema);
+        } catch (GenerationException e) {
+            throw new CoderRuntimeException(e);
+        }
     }
 
     @Override
     protected String toPrettyJson(Object object) {
-        /*
-         * The validator strips off the "pretty" stuff (i.e., spaces), thus we have to validate and generate the pretty
-         * JSON in separate steps.
-         */
-        gson.toJson(object, object.getClass(), validatorApi.createJsonWriter(validator, new StringWriter()));
-
+        try {
+            validate(gsonPretty.toJson(object));
+        } catch (CoderException e) {
+            throw new CoderRuntimeException(e);
+        }
         return super.toPrettyJson(object);
     }
 
@@ -79,18 +77,28 @@ public class StandardValCoder extends StandardCoder {
 
     @Override
     protected void toJson(@NonNull Writer target, @NonNull Object object) {
-        gson.toJson(object, object.getClass(), validatorApi.createJsonWriter(validator, target));
+        try {
+            validate(gson.toJson(object));
+        } catch (CoderException e) {
+            throw new CoderRuntimeException(e);
+        }
+        gson.toJson(object, object.getClass(), target);
     }
 
     @Override
     protected <T> T fromJson(@NonNull Reader source, @NonNull Class<T> clazz) {
-        return convertFromDouble(clazz, gson.fromJson(validatorApi.createJsonReader(validator, source), clazz));
+        return convertFromDouble(clazz, gson.fromJson(source, clazz));
     }
 
     @Override
     protected <T> T fromJson(String json, Class<T> clazz) {
+        try {
+            validate(json);
+        } catch (CoderException e) {
+            throw new CoderRuntimeException(e);
+        }
         var reader = new StringReader(json);
-        return convertFromDouble(clazz, gson.fromJson(validatorApi.createJsonReader(validator, reader), clazz));
+        return convertFromDouble(clazz, gson.fromJson(reader, clazz));
     }
 
     /**
@@ -99,21 +107,32 @@ public class StandardValCoder extends StandardCoder {
     public boolean isConformant(@NonNull String json) {
         try {
             conformance(json);
-        } catch (CoderException e) {
-            logger.info("JSON is not conformant to schema", e);
+            return true;
+        } catch (Exception e) {
+            logger.error("JSON is not conformant to schema", e);
             return false;
         }
-        return true;
     }
 
     /**
      * Check a json string for conformance against its schema definition.
      */
     public void conformance(@NonNull String json) throws CoderException {
+        validate(json);
+    }
+
+    private void validate(Object object) throws CoderException {
         try {
-            validatorApi.parseAll(validatorApi.createJsonReader(validator, new StringReader(json)));
-        } catch (ValidationFailedException e) {
-            throw new CoderException(e);
+            final var validator = new Validator();
+            validator.validate(schema, object);
+        } catch (ValidationException exception) {
+            var error = String.format("JSON validation failed: %s", exception.getMessage());
+            logger.error(error);
+            throw new CoderException(error);
         }
     }
+
+    private void validate(String json) throws CoderException {
+        validate(gson.fromJson(json, Object.class));
+    }
 }
index d94ddca..8ee2e81 100644 (file)
@@ -3,6 +3,7 @@
  * ONAP
  * ================================================================================
  * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,8 +25,8 @@ import java.io.Reader;
 import java.io.Writer;
 
 /**
- * YAML encoder and decoder using the "standard" mechanism, which is currently gson. All
- * of the methods perform conversion to/from YAML (instead of JSON).
+ * YAML encoder and decoder using the "standard" mechanism, which is currently gson.
+ * All the methods perform conversion to/from YAML (instead of JSON).
  */
 public class StandardYamlCoder extends StandardCoder {
     private final YamlJsonTranslator translator;
index f13ddd9..6db9e66 100644 (file)
@@ -87,7 +87,7 @@ class StandardCoderInstantAsMillisTest {
     }
 
     @Test
-    void testToJsonTree_testFromJsonJsonElementClassT() throws Exception {
+    void testToJsonTree_testFromJsonJsonElementClassT() {
         MyMap map = new MyMap();
         map.props = new LinkedHashMap<>();
         map.props.put("jel keyA", "jel valueA");
index 45d814a..8b5f406 100644 (file)
 
 package org.onap.policy.common.utils.coder;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
-import com.worldturner.medeia.api.ValidationFailedException;
-import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.util.List;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.onap.policy.common.utils.resources.ResourceUtils;
 
 class StandardValCoderTest {
     private String jsonSchema;
@@ -63,7 +62,7 @@ class StandardValCoderTest {
     }
 
     @BeforeEach
-    public void testSetUp() throws Exception {
+    public void testSetUp() {
         jsonSchema = getJson("src/test/resources/org/onap/policy/common/utils/coder/test.schema.json");
         validJson = getJson("src/test/resources/org/onap/policy/common/utils/coder/valid.json");
         missingReqJson = getJson("src/test/resources/org/onap/policy/common/utils/coder/missing-required.json");
@@ -72,7 +71,7 @@ class StandardValCoderTest {
 
     @Test
     void testDecode() throws CoderException {
-        StandardValCoder valCoder = new StandardValCoder(jsonSchema, "test-schema");
+        StandardValCoder valCoder = new StandardValCoder(jsonSchema);
 
         ValOuter valOuter = valCoder.decode(validJson, ValOuter.class);
         assertValidJson(valOuter);
@@ -85,34 +84,22 @@ class StandardValCoderTest {
             valCoder.decode(missingReqJson, ValOuter.class);
             fail("missing required field should have been flagged by the schema validation");
         } catch (CoderException e) {
-            assertEquals("required", ((ValidationFailedException) e.getCause()).getFailures().get(0).getRule());
-            assertEquals("aaCollection",
-                ((ValidationFailedException) e.getCause()).getFailures().get(0).getProperty());
-            assertEquals("Required property aaCollection is missing from object",
-                ((ValidationFailedException) e.getCause()).getFailures().get(0).getMessage());
+            assertThat(e.getMessage()).contains("Missing property aaCollection");
         }
 
         try {
             valCoder.decode(badRegexJson, ValOuter.class);
             fail("bad regex should have been flagged by the schema validation");
         } catch (CoderException e) {
-            assertEquals("properties", ((ValidationFailedException) e.getCause()).getFailures().get(0).getRule());
-            assertEquals("aaString",
-                    ((ValidationFailedException) e.getCause()).getFailures().get(0).getProperty());
-            assertEquals("Property validation failed",
-                    ((ValidationFailedException) e.getCause()).getFailures().get(0).getMessage());
-            assertEquals("pattern",
-                    ((ValidationFailedException) e.getCause()).getFailures()
-                            .get(0).getDetails().iterator().next().getRule());
-            assertEquals("Pattern ^([a-z]*)$ is not contained in text",
-                    ((ValidationFailedException) e.getCause()).getFailures()
-                            .get(0).getDetails().iterator().next().getMessage());
+            assertThat(e.getMessage())
+                .contains("Validation errors: \"abc123\" at #/aaString failed")
+                .contains("Did not match pattern: ^([a-z]*)$");
         }
     }
 
     @Test
     void testEncode() throws CoderException {
-        StandardValCoder valCoder = new StandardValCoder(jsonSchema, "test-schema");
+        StandardValCoder valCoder = new StandardValCoder(jsonSchema);
         ValOuter valOuter = valCoder.decode(validJson, ValOuter.class);
 
         String valOuterJson = valCoder.encode(valOuter);
@@ -129,7 +116,7 @@ class StandardValCoderTest {
 
     @Test
     void testPretty() throws CoderException {
-        StandardValCoder valCoder = new StandardValCoder(jsonSchema, "test-schema");
+        StandardValCoder valCoder = new StandardValCoder(jsonSchema);
         ValOuter valOuter = valCoder.decode(validJson, ValOuter.class);
 
         String valOuterJson = valCoder.encode(valOuter);
@@ -146,12 +133,33 @@ class StandardValCoderTest {
 
     @Test
     void testConformance() {
-        StandardValCoder valCoder = new StandardValCoder(jsonSchema, "test-schema");
+        StandardValCoder valCoder = new StandardValCoder(jsonSchema);
         assertTrue(valCoder.isConformant(validJson));
         assertFalse(valCoder.isConformant(missingReqJson));
         assertFalse(valCoder.isConformant(badRegexJson));
     }
 
+    @Test
+    void testNullValues() throws CoderException {
+        StandardValCoder valCoder = new StandardValCoder(jsonSchema);
+        assertThrows(NullPointerException.class, () -> valCoder.toJson(null));
+        assertThrows(NullPointerException.class, () -> valCoder.isConformant(null));
+        assertThrows(NullPointerException.class, () -> valCoder.conformance(null));
+
+        assertThrows(NullPointerException.class, () -> valCoder.toJson(null, null));
+        var writer = new StringWriter();
+        assertThrows(NullPointerException.class, () -> valCoder.toJson(writer, null));
+        ValOuter valOuter = valCoder.decode(validJson, ValOuter.class);
+        assertThrows(NullPointerException.class, () -> valCoder.toJson(null, valOuter));
+    }
+
+    @Test
+    void testConstructor() {
+        assertThrows(NullPointerException.class, () -> new StandardValCoder(null));
+
+        assertThrows(CoderRuntimeException.class, () -> new StandardValCoder("$schema"));
+    }
+
     private void assertValidJson(ValOuter valOuter) {
         assertEquals("abcd", valOuter.getAaString());
         assertEquals(90, valOuter.getAnInteger());
@@ -160,7 +168,7 @@ class StandardValCoderTest {
         assertEquals(Integer.valueOf(1200), valOuter.getAaCollection().get(0).getSubItemInteger());
     }
 
-    private String getJson(String filePath) throws IOException {
-        return new String(Files.readAllBytes(Paths.get(filePath)));
+    private String getJson(String filePath) {
+        return ResourceUtils.getResourceAsString(filePath);
     }
 }
\ No newline at end of file
index e79475e..60e70fb 100644 (file)
@@ -1,71 +1,71 @@
 {
-    "definitions": {},
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "$id": "http://onap.org/policy/common/coders/root.json",
-    "type": "object",
-    "title": "Test Schema",
-    "required": [
-        "aaString",
-        "anInteger",
-        "aaBoolean",
-        "aaCollection"
-    ],
-    "properties": {
-        "aaString": {
-            "$id": "#/properties/aaString",
+  "definitions": {},
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "http://onap.org/policy/common/coders/root.json",
+  "type": "object",
+  "title": "Test Schema",
+  "required": [
+    "aaString",
+    "anInteger",
+    "aaBoolean",
+    "aaCollection"
+  ],
+  "properties": {
+    "aaString": {
+      "$id": "#/properties/aaString",
+      "type": "string",
+      "title": "an alphabetical string",
+      "default": "",
+      "examples": [
+        "abcdef"
+      ],
+      "pattern": "^([a-z]*)$"
+    },
+    "anInteger": {
+      "$id": "#/properties/anInteger",
+      "type": "integer",
+      "title": "a bounded integer",
+      "default": 5,
+      "examples": [
+        98
+      ],
+      "minimum": 10,
+      "maximum": 100
+    },
+    "aaBoolean": {
+      "$id": "#/properties/aaBoolean",
+      "type": "boolean",
+      "title": "a boolean",
+      "default": false,
+      "examples": [
+        true
+      ]
+    },
+    "aaCollection": {
+      "$id": "#/properties/aaCollection",
+      "type": "array",
+      "title": "a collection",
+      "items": {
+        "$id": "#/properties/aaCollection/items",
+        "type": "object",
+        "title": "the collection items",
+        "required": [
+          "subItemString"
+        ],
+        "properties": {
+          "subItemString": {
+            "$id": "#/properties/aaCollection/items/properties/subItemString",
             "type": "string",
-            "title": "an alphabetical string",
-            "default": "",
-            "examples": [
-                "abcdef"
-            ],
-            "pattern": "^([a-z]*)$"
-        },
-        "anInteger": {
-            "$id": "#/properties/anInteger",
-            "type": "integer",
-            "title": "a bounded integer",
-            "default": 5,
-            "examples": [
-                98
-            ],
-            "minimum": 10,
-            "maximum": 100
-        },
-        "aaBoolean": {
-            "$id": "#/properties/aaBoolean",
-            "type": "boolean",
-            "title": "a boolean",
-            "default": false,
-            "examples": [
-                true
-            ]
-        },
-        "aaCollection": {
-            "$id": "#/properties/aaCollection",
-            "type": "array",
-            "title": "a collection",
-            "items": {
-                "$id": "#/properties/aaCollection/items",
-                "type": "object",
-                "title": "the collection items",
-                "required": [
-                    "subItemString"
-                ],
-                "properties": {
-                    "subItemString": {
-                        "$id": "#/properties/aaCollection/items/properties/subItemString",
-                        "type": "string",
-                        "title": "the subitem string",
-                        "default": "blah",
-                        "pattern": "^(.*)$"
-                    },
-                    "subItemInteger": {
-                        "$id": "#/properties/aaCollection/items/properties/subItemInteger",
-                        "type": "integer"
-                    }
-                }
-            }
+            "title": "the subitem string",
+            "default": "blah",
+            "pattern": "^(.*)$"
+          },
+          "subItemInteger": {
+            "$id": "#/properties/aaCollection/items/properties/subItemInteger",
+            "type": "integer"
+          }
         }
+      }
     }
+  }
 }
\ No newline at end of file
index c173817..a2cdb93 100644 (file)
@@ -1,9 +1,11 @@
 {
-    "aaString": "abcd",
-    "anInteger": 90,
-    "aaBoolean": true,
-    "aaCollection": [ {
-        "subItemString": "defg",
-        "subItemInteger": 1200
-    }]
+  "aaString": "abcd",
+  "anInteger": 90,
+  "aaBoolean": true,
+  "aaCollection": [
+    {
+      "subItemString": "defg",
+      "subItemInteger": 1200
+    }
+  ]
 }
\ No newline at end of file