Add support for JSON files (recognized by '.json' suffix).
diff --git a/pom.xml b/pom.xml
index e51e8ee..687beda 100644
--- a/pom.xml
+++ b/pom.xml
@@ -101,6 +101,11 @@
       <version>2.3</version>
     </dependency>
     <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>2.1.1</version>
+    </dependency>
+    <dependency>
       <groupId>sqlline</groupId>
       <artifactId>sqlline</artifactId>
       <version>1.1.7</version>
diff --git a/src/main/java/net/hydromatic/optiq/impl/csv/CsvSchema.java b/src/main/java/net/hydromatic/optiq/impl/csv/CsvSchema.java
index 9f2e289..1b81bbf 100644
--- a/src/main/java/net/hydromatic/optiq/impl/csv/CsvSchema.java
+++ b/src/main/java/net/hydromatic/optiq/impl/csv/CsvSchema.java
@@ -52,7 +52,7 @@
     File[] files = directoryFile.listFiles(
         new FilenameFilter() {
           public boolean accept(File dir, String name) {
-            return name.endsWith(".csv");
+            return name.endsWith(".csv") || name.endsWith(".json");
           }
         });
     if (files == null) {
@@ -61,6 +61,13 @@
     }
     for (File file : files) {
       String tableName = file.getName();
+      if (tableName.endsWith(".json")) {
+        tableName = tableName.substring(
+            0, tableName.length() - ".json".length());
+        JsonTable table = new JsonTable(file);
+        builder.put(tableName, table);
+        continue;
+      }
       if (tableName.endsWith(".csv")) {
         tableName = tableName.substring(
             0, tableName.length() - ".csv".length());
diff --git a/src/main/java/net/hydromatic/optiq/impl/csv/CsvTableScan.java b/src/main/java/net/hydromatic/optiq/impl/csv/CsvTableScan.java
index d95edbf..fbf039f 100644
--- a/src/main/java/net/hydromatic/optiq/impl/csv/CsvTableScan.java
+++ b/src/main/java/net/hydromatic/optiq/impl/csv/CsvTableScan.java
@@ -80,6 +80,13 @@
             getRowType(),
             pref.preferArray());
 
+    if (table instanceof JsonTable) {
+      return implementor.result(
+          physType,
+          Blocks.toBlock(
+              Expressions.call(table.getExpression(JsonTable.class),
+                  "enumerable")));
+    }
     return implementor.result(
         physType,
         Blocks.toBlock(
diff --git a/src/main/java/net/hydromatic/optiq/impl/csv/JsonEnumerator.java b/src/main/java/net/hydromatic/optiq/impl/csv/JsonEnumerator.java
new file mode 100644
index 0000000..66859fe
--- /dev/null
+++ b/src/main/java/net/hydromatic/optiq/impl/csv/JsonEnumerator.java
@@ -0,0 +1,68 @@
+/*
+// Licensed to Julian Hyde under one or more contributor license
+// agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership.
+//
+// Julian Hyde 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 net.hydromatic.optiq.impl.csv;
+
+import net.hydromatic.linq4j.Enumerator;
+import net.hydromatic.linq4j.Linq4j;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.*;
+import java.util.List;
+
+/** Enumerator that reads from a JSON file. */
+class JsonEnumerator implements Enumerator<Object> {
+  private final Enumerator<Object> enumerator;
+
+  public JsonEnumerator(File file) {
+    try {
+      final ObjectMapper mapper = new ObjectMapper();
+      mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+      mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+      mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+      //noinspection unchecked
+      List<Object> list = mapper.readValue(file, List.class);
+      enumerator = Linq4j.enumerator(list);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public Object current() {
+    return enumerator.current();
+  }
+
+  public boolean moveNext() {
+    return enumerator.moveNext();
+  }
+
+  public void reset() {
+    enumerator.reset();
+  }
+
+  public void close() {
+    try {
+      enumerator.close();
+    } catch (Exception e) {
+      throw new RuntimeException("Error closing JSON reader", e);
+    }
+  }
+}
+
+// End JsonEnumerator.java
diff --git a/src/main/java/net/hydromatic/optiq/impl/csv/JsonTable.java b/src/main/java/net/hydromatic/optiq/impl/csv/JsonTable.java
new file mode 100644
index 0000000..d9510a6
--- /dev/null
+++ b/src/main/java/net/hydromatic/optiq/impl/csv/JsonTable.java
@@ -0,0 +1,94 @@
+/*
+// Licensed to Julian Hyde under one or more contributor license
+// agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership.
+//
+// Julian Hyde 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 net.hydromatic.optiq.impl.csv;
+
+import net.hydromatic.optiq.*;
+import net.hydromatic.optiq.impl.AbstractTableQueryable;
+import net.hydromatic.optiq.impl.java.AbstractQueryableTable;
+import net.hydromatic.optiq.rules.java.EnumerableConvention;
+import net.hydromatic.optiq.rules.java.JavaRules;
+
+import net.hydromatic.linq4j.*;
+
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.relopt.RelOptTable;
+import org.eigenbase.reltype.*;
+import org.eigenbase.sql.type.SqlTypeName;
+
+import java.io.*;
+
+/**
+ * Table based on a JSON file.
+ */
+public class JsonTable extends AbstractQueryableTable
+    implements TranslatableTable {
+  private final File file;
+
+  /** Creates a JsonTable. */
+  JsonTable(File file) {
+    super(Object[].class);
+    this.file = file;
+  }
+
+  public String toString() {
+    return "JsonTable";
+  }
+
+  public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+    return typeFactory.builder().add("_MAP",
+        typeFactory.createMapType(
+            typeFactory.createSqlType(SqlTypeName.VARCHAR),
+            typeFactory.createSqlType(SqlTypeName.ANY))).build();
+  }
+
+  public Statistic getStatistic() {
+    return Statistics.UNKNOWN;
+  }
+
+  public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
+      SchemaPlus schema, String tableName) {
+    return new AbstractTableQueryable<T>(queryProvider, schema, this,
+        tableName) {
+      public Enumerator<T> enumerator() {
+        //noinspection unchecked
+        return (Enumerator<T>) new JsonEnumerator(file);
+      }
+    };
+  }
+
+  /** Returns an enumerable over the file. */
+  public Enumerable<Object> enumerable() {
+    return new AbstractEnumerable<Object>() {
+      public Enumerator<Object> enumerator() {
+        return new JsonEnumerator(file);
+      }
+    };
+  }
+
+  public RelNode toRel(
+      RelOptTable.ToRelContext context,
+      RelOptTable relOptTable) {
+    return new JavaRules.EnumerableTableAccessRel(
+        context.getCluster(),
+        context.getCluster().traitSetOf(EnumerableConvention.INSTANCE),
+        relOptTable,
+        (Class) getElementType());
+  }
+}
+
+// End JsonTable.java
diff --git a/src/test/java/net/hydromatic/optiq/test/CsvTest.java b/src/test/java/net/hydromatic/optiq/test/CsvTest.java
index 47cec3b..a4749fd 100644
--- a/src/test/java/net/hydromatic/optiq/test/CsvTest.java
+++ b/src/test/java/net/hydromatic/optiq/test/CsvTest.java
@@ -145,6 +145,15 @@
         "NAME=Alice; EMPNO=130");
   }
 
+  @Test public void testJson() throws SQLException {
+    checkSql("model", "select _MAP['id'] as id,\n"
+        + " _MAP['title'] as title,\n"
+        + " CHAR_LENGTH(CAST(_MAP['title'] AS VARCHAR(30))) as len\n"
+        + " from \"archers\"",
+        "ID=19990101; TITLE=Washday blues.; LEN=14",
+        "ID=19990103; TITLE=Daniel creates a drama.; LEN=23");
+  }
+
   private void checkSql(String model, String sql) throws SQLException {
     checkSql(sql, model, output());
   }
diff --git a/src/test/resources/sales/archers.json b/src/test/resources/sales/archers.json
new file mode 100644
index 0000000..47dd0c5
--- /dev/null
+++ b/src/test/resources/sales/archers.json
@@ -0,0 +1,32 @@
+[
+{
+  id: "19990101",
+  dow: "Friday",
+  longDate: "New Years Day",
+  title: "Washday blues.",
+  characters: [ "Helen", "Pat", "Sid", "Kathy", "Eddie", "Clarrie", "Joe " ],
+  script: "Louise Page",
+  summary: "",
+  lines: [
+    "It would have been John's birthday yesterday and the family celebrated New Year together.",
+    "Sid's determined to get fit in the New Year, starting by jogging around the green a few times! He even wants Kathy to join him in a joint membership at Grey Gables' gym! They could even do Clarrie a favour by paying her to mind Jamie while they are out. She, though, is less convinced by his resolve.",
+    "Helen seems more than a little unsure about Tommy and Hayley's pig business, complaining about everything from her perception that a 50-50 split in the profits is harsh on Tommy (after all, Hayley is still living in Birmingham) to the way that the problems over the Christmas pork would reflect badly on Pat and Tony's carefully crafted Bridge Farm reputation. She seems to think that absorbing it into the farm would make proper business sense.",
+    "Still no new washing machine at Grange Farm, but Eddie, at least is not feeling too down. He's even dreaming of next year's New Year's Eve party - only Clarrie isn't confident that they'll still be there."
+  ]
+},
+{
+  id: "19990103",
+  dow: "Sunday",
+  longDate: "Sunday 3rd January",
+  title: "Daniel creates a drama.",
+  characters: [ "Neil", "Susan", "Clarrie", "Debbie", "Brian", "Phil", "Jill" ],
+  script: "Christopher Hawes",
+  summary: "",
+  lines: [
+    "Neil and Susan are a little nervous about their finances, especially given Neil's lack of casual work from Home Farm. Fortunately Debbie has had a word with Brian about their lack of co-ordination and has got a promise from him to liase more closely in future. She plans to offer Neil some work in the lambing sheds - it's not much, but she hopes it might placate him. Susan, of course, just points at the mistake as another way that Brian has let them down.",
+    "Clarrie's washing machine is still unfixed and she borrows Susan's to do some spinning of her wet washing. She uses the opportunity to have a well-deserved (and rare) moan at her situation - especially at the recently received electricity and telephone bills and the cost of teenage sons!",
+    "The whole village seems to have enjoyed the panto, although Phil's not sure whether the shambling production will escape some Snellish vitriol when she writes her review for the Echo. Elsewhere, Baggy and Snatch's hilarious Pantomime Cow (in a horse's costume) seems to have surprised many people with its lack of crudeness!",
+    "Phil and Jill are still appreciating having Daniel to stay, but he's playing up a little, using his illness to excuse some late night crying and whinging. Jill's expecting Shula to phone but doesn't plan on worrying her with this news, but will take him to the doctor in the morning, just in case it's not a ruse."
+  ]
+}
+]