Add a random JSON structure in query results, to simulate unstructured content
diff --git a/org.apache.sling.graphql.samples.website/README.md b/org.apache.sling.graphql.samples.website/README.md
index 3bef8c0..b1ddef4 100644
--- a/org.apache.sling.graphql.samples.website/README.md
+++ b/org.apache.sling.graphql.samples.website/README.md
@@ -88,6 +88,50 @@
     
     <%@include file="/apps/samples/common/GQLschema.jsp" %>
 
+
+## Unstructured content
+
+Sling applications often deal with unstructred or semi-structured content which
+doesn't have a strict schema.
+
+To demonstrate how this works with GraphQL queries, in this sample you can use a
+query such as
+
+    {
+      navigation {
+        root
+      }
+      random
+    }
+
+Which includes a randomly generated hierarchical structure, to test how GraphQL
+clients cope (they should - it's part of the standard) with results such as
+
+    {
+      "data": {
+        "navigation": {
+          "root": "/content/articles"
+        },
+        "random": {
+          "key1": 112,
+          "sub2": {
+            "key1": false,
+            "sub2": {
+              "key1": true
+            },
+            "key5": [
+              true,
+              true
+            ],
+            "key3": true
+          }
+        }
+      }
+    }
+
+where the "shape" and content of the `random` element can vary widely, simulating
+varying content structures.
+
 ## How to run this
 
 Build and run with
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/RandomDataFetcher.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/RandomDataFetcher.java
new file mode 100644
index 0000000..c0f3cc4
--- /dev/null
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/RandomDataFetcher.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sling.graphql.samples.website.datafetchers;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.sling.graphql.api.SlingDataFetcher;
+import org.apache.sling.graphql.api.SlingDataFetcherEnvironment;
+import org.osgi.service.component.annotations.Component;
+
+/** Return a nested Map of random data, for testing unpredictable structures */
+@Component(service = SlingDataFetcher.class, property = {"name=website/random"})
+public class RandomDataFetcher implements SlingDataFetcher<Object> {
+
+    private final Random random = new Random(42);
+
+    private Object[] randomArray() {
+        final Object[] result = new Object[random.nextInt(3) + 1];
+        for(int i=0; i < result.length; i++) {
+            result[i] = randomValue();
+        }
+        return result;
+    }
+
+    private Object randomValue() {
+        switch(random.nextInt(4)) {
+            case 0: return "It is now " + new Date();
+            case 1: return random.nextInt(2) > 0;
+            case 2: return randomArray();
+            default: return random.nextInt(451);
+        }
+    }
+
+    private Map<String, Object> randomMap(int maxEntries) {
+        int counter=1;
+        final Map<String, Object> result = new HashMap<>();
+        while(maxEntries > 0) {
+            result.put("key" + counter++, randomValue());
+            if(random.nextInt(2) == 1) {
+                final int maxSub = random.nextInt(maxEntries) / (random.nextInt(2) + 1);
+                result.put("sub" + counter++, randomMap(maxSub));
+                maxEntries -= maxSub;
+            }
+            maxEntries--;
+        }
+        return result;
+    }
+
+    @Override
+    public Object get(SlingDataFetcherEnvironment env) throws Exception {
+        Map<String, Object> result = randomMap(random.nextInt(24));
+        result.put(
+            "info", 
+            "The contents of this map are random, to demonstrate unpredictable content structures"
+        );
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/scalars/ObjectScalar.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/scalars/ObjectScalar.java
new file mode 100644
index 0000000..79271c8
--- /dev/null
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/scalars/ObjectScalar.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.sling.graphql.samples.website.scalars;
+
+import org.apache.sling.graphql.api.ScalarConversionException;
+import org.apache.sling.graphql.api.SlingScalarConverter;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Component;
+
+/** Passthrough Scalar, used to output unpredictable JSON structures */
+@Component(service = SlingScalarConverter.class, property = { "name=Object" })
+public class ObjectScalar implements SlingScalarConverter<Object, Object> {
+
+    @Override
+    public @Nullable Object parseValue(@Nullable Object input) throws ScalarConversionException {
+        return input;
+    }
+
+    @Override
+    public @Nullable Object serialize(@Nullable Object value) throws ScalarConversionException {
+        return value;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/servlet/GQLschema.jsp b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/servlet/GQLschema.jsp
index a085489..d2313a3 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/servlet/GQLschema.jsp
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/servlet/GQLschema.jsp
@@ -19,11 +19,16 @@
 
 <%@include file="../common/directives.jsp" %>
 
+scalar Object
+
 type Query {
   <%@include file="../common/common-query-parts.jsp" %>
   
   # List of Articles which contain the supplied text
   article (withText : String) : [Article] @fetcher(name:"website/articlesWithText")
+
+  # Testing a Scalar that returns an unpredictable JSON structure
+  random : Object @fetcher(name:"website/random")
 }
 
 <%@include file="/apps/samples/common/GQLschema.jsp" %>
\ No newline at end of file