You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2019/07/10 17:20:36 UTC

[sling-whiteboard] branch master updated: SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations

This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new ae4f82e  SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations
ae4f82e is described below

commit ae4f82eb7b4b34801d6555724e31dd0727812269
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed Jul 10 19:19:43 2019 +0200

    SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations
    
    * extracted API bundle
    * first attempt at implementing a separate JSON content parser
---
 .../org-apache-sling-contentparser-api/pom.xml     |  61 +++++
 .../sling/contentparser/api/ContentHandler.java    |  41 +++
 .../sling/contentparser/api/ContentParser.java     |  79 ++++++
 .../sling/contentparser/api/JsonParserFeature.java |  36 +++
 .../sling/contentparser/api/ParseException.java    |  49 ++++
 .../sling/contentparser/api/ParserHelper.java      | 104 ++++++++
 .../sling/contentparser/api/ParserOptions.java     | 171 +++++++++++++
 .../sling/contentparser/api/package-info.java      |  22 ++
 .../sling/contentparser/api/ParserHelperTest.java  |  57 +++++
 .../org-apache-sling-contentparser-json/pom.xml    | 112 +++++++++
 .../json/internal/JsonContentParser.java           | 212 ++++++++++++++++
 .../json/internal/JsonTicksConverter.java          | 106 ++++++++
 .../json/internal/JsonContentParserTest.java       | 186 ++++++++++++++
 .../contentparser/json/internal/TestUtils.java     |  60 +++++
 .../json/internal/mapsupport/ContentElement.java   |  52 ++++
 .../internal/mapsupport/ContentElementHandler.java |  69 ++++++
 .../internal/mapsupport/ContentElementImpl.java    |  68 +++++
 .../src/test/resources/content-test/content.json   | 274 +++++++++++++++++++++
 .../invalid-test/contentWithObjectList.json        |  14 ++
 .../src/test/resources/invalid-test/invalid.json   |   1 +
 20 files changed, 1774 insertions(+)

diff --git a/contentparser/org-apache-sling-contentparser-api/pom.xml b/contentparser/org-apache-sling-contentparser-api/pom.xml
new file mode 100644
index 0000000..799b201
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.contentparser.api</artifactId>
+    <version>0.9.0-SNAPSHOT</version>
+
+    <name>Apache Sling Content Parser API</name>
+    <description>
+        Parser API Apache Sling Resource trees stored in files (e.g. JSON, FileVault XML, etc.).
+    </description>
+
+    <scm>
+        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-api.git</connection>
+        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-api.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-contentparser-api.git</url>
+      <tag>HEAD</tag>
+  </scm>
+
+    <build>
+        <plugins>
+            
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ContentHandler.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ContentHandler.java
new file mode 100644
index 0000000..34103d1
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ContentHandler.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.contentparser.api;
+
+import java.util.Map;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * A {@code ContentHandler} gets notified while parsing content with a {@link ContentParser}. The resources are always reported in the
+ * order in which they are found in the parsed files. Parents are always reported before their children.
+ */
+@ConsumerType
+public interface ContentHandler {
+
+    /**
+     * Resource found in parsed content.
+     *
+     * @param path       the path of the found resource inside the content fragment; the root resource from the content fragment will
+     *                   have the {@code "/"} path
+     * @param properties the resource's properties
+     */
+    void resource(String path, Map<String, Object> properties);
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ContentParser.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ContentParser.java
new file mode 100644
index 0000000..1a684ca
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ContentParser.java
@@ -0,0 +1,79 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.api;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A {@code ContentParser} parses Sling resource trees from a file. Implementations have to be thread-safe. A consumer requiring a {@code
+ * ContentParser} reference should filter based on the {@link #SERVICE_PROPERTY_CONTENT_TYPE} in order to get a content type specific
+ * parser.
+ */
+@ProviderType
+public interface ContentParser {
+
+    /**
+     * JSON content descriptor file.
+     *
+     * @see <a href="https://sling.apache.org/documentation/bundles/content-loading-jcr-contentloader.html#json-descriptor-files">JCR
+     * ContentLoader JSON descriptor files</a>
+     */
+    String JSON_CONTENT_TYPE = "json";
+
+    /**
+     * XML content descriptor file.
+     *
+     * @see <a href="https://sling.apache.org/documentation/bundles/content-loading-jcr-contentloader.html#xml-descriptor-files">JCR
+     * ContentLoader XML descriptor files</a>
+     */
+    String XML_CONTENT_TYPE = "xml";
+
+    /**
+     * JCR XML content (FileVault XML),aAlso known as extended document view XML. Extends the regular document view as specified by JCR 2.0
+     * with specifics like multi-value and type information.
+     *
+     * @see <a href="https://docs.adobe.com/content/docs/en/spec/jcr/2.0/7_Export.html#7.3%20Document%20View">JCR 2.0, 7.3 Document View</a>
+     * @see <a href="http://jackrabbit.apache.org/filevault/">Jackrabbit FileVault</a>
+     */
+    String JCR_XML_CONTENT_TYPE = "jcr.xml";
+
+    /**
+     * OSGi service registration property indicating the content type this {@code ContentParser} supports.
+     *
+     * @see #JSON_CONTENT_TYPE
+     * @see #XML_CONTENT_TYPE
+     * @see #JCR_XML_CONTENT_TYPE
+     */
+    String SERVICE_PROPERTY_CONTENT_TYPE = "org.apache.sling.contentparser.content_type";
+
+    /**
+     * Parse content in a "stream-based" way. Each resource that is found in the content is reported to the {@link ContentHandler}.
+     *
+     * @param contentHandler content handler that accepts the parsed content
+     * @param inputStream    stream with serialized content
+     * @param parserOptions  parser options, providing different settings for handling the serialized content
+     * @throws IOException    when an I/O error occurs
+     * @throws ParseException when a parsing error occurs.
+     */
+    void parse(ContentHandler contentHandler, InputStream inputStream, ParserOptions parserOptions) throws IOException, ParseException;
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/JsonParserFeature.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/JsonParserFeature.java
new file mode 100644
index 0000000..58c3e2b
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/JsonParserFeature.java
@@ -0,0 +1,36 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.api;
+
+/**
+ * Feature flags for parsing JSON files.
+ */
+public enum JsonParserFeature {
+
+    /**
+     * Support comments (&#47;* ... *&#47;) in JSON files.
+     */
+    COMMENTS,
+
+    /**
+     * Support ticks (') additional to double quotes (") as quoting symbol for JSON names and strings.
+     */
+    QUOTE_TICK
+    
+}
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParseException.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParseException.java
new file mode 100644
index 0000000..4a19711
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParseException.java
@@ -0,0 +1,49 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.api;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Defines a parsing exception encountered by a {@link ContentParser}.
+ */
+@ProviderType
+public final class ParseException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Builds a {@code ParseException}, providing a message.
+     *
+     * @param message the message
+     */
+    public ParseException(String message) {
+        super(message);
+    }
+
+    /**
+     * Builds a {@code ParseException}, providing a message and a cause.
+     *
+     * @param message the message
+     * @param cause   the cause
+     */
+    public ParseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParserHelper.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParserHelper.java
new file mode 100644
index 0000000..7cb1196
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParserHelper.java
@@ -0,0 +1,104 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.api;
+
+import java.lang.reflect.Array;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+@ConsumerType
+public final class ParserHelper {
+
+    public static final String ECMA_DATE_FORMAT = "EEE MMM dd yyyy HH:mm:ss 'GMT'Z";
+    public static final String ISO_8601_MILLISECONDS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSVV";
+    public static final Locale DATE_FORMAT_LOCALE = Locale.US;
+    public static final DateTimeFormatter ECMA_DATE_FORMATTER = DateTimeFormatter.ofPattern(ECMA_DATE_FORMAT, DATE_FORMAT_LOCALE);
+    public static final DateTimeFormatter ISO_8601_MILLISECONDS_DATE_FORMATTER =
+            DateTimeFormatter.ofPattern(ISO_8601_MILLISECONDS_DATE_FORMAT,
+                    DATE_FORMAT_LOCALE);
+
+    /**
+     * Attempts to parse a {@code string} using first the {@link #ISO_8601_MILLISECONDS_DATE_FORMAT} format and then the {@link
+     * #ECMA_DATE_FORMAT}.
+     *
+     * @param string the string to parse
+     * @return a {@link Calendar} containing the parsed date or {@code null}, if the parsing failed
+     */
+    public static Calendar parseDate(String string) {
+        Calendar calendar = Calendar.getInstance();
+        try {
+            final OffsetDateTime offsetDateTime = OffsetDateTime.parse(string, ISO_8601_MILLISECONDS_DATE_FORMATTER);
+            final Instant instant = offsetDateTime.toInstant();
+            calendar.setTime(Date.from(instant));
+            calendar.setTimeZone(TimeZone.getTimeZone(offsetDateTime.getOffset()));
+        } catch (DateTimeParseException e) {
+            try {
+                final OffsetDateTime offsetDateTime = OffsetDateTime.parse(string, ECMA_DATE_FORMATTER);
+                final Instant instant = offsetDateTime.toInstant();
+                calendar.setTime(Date.from(instant));
+                calendar.setTimeZone(TimeZone.getTimeZone(offsetDateTime.getOffset()));
+            } catch (DateTimeParseException ee) {
+                calendar = null;
+            }
+        }
+        return calendar;
+    }
+
+    /**
+     * Converts a multi-value property to a single object.
+     *
+     * @param values the multi-value property's values
+     * @return an object representation of the multi-value property
+     */
+    public static Object convertSingleTypeArray(Object[] values) {
+        if (values.length == 0) {
+            return values;
+        }
+        Class<?> itemType = null;
+        for (Object value : values) {
+            if (value == null) {
+                throw new ParseException("Multi-value array must not contain null values.");
+            }
+            if (value instanceof Map) {
+                throw new ParseException("Multi-value array must not contain maps/objects.");
+            }
+            if (itemType == null) {
+                itemType = value.getClass();
+            } else if (itemType != value.getClass()) {
+                throw new ParseException("Multivalue array must not contain values with different types "
+                        + "(" + itemType.getName() + ", " + value.getClass().getName() + ").");
+            }
+        }
+        Object convertedArray = Array.newInstance(itemType, values.length);
+        for (int i = 0; i < values.length; i++) {
+            Array.set(convertedArray, i, values[i]);
+        }
+        return convertedArray;
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParserOptions.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParserOptions.java
new file mode 100644
index 0000000..ebf80f0
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/ParserOptions.java
@@ -0,0 +1,171 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.api;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Options for content parsers.
+ */
+@ProviderType
+public final class ParserOptions {
+
+    /**
+     * Default primary type.
+     */
+    public static final String DEFAULT_PRIMARY_TYPE = "nt:unstructured";
+
+    /**
+     * Default list of prefixes to remove from property names.
+     */
+    public static final Set<String> DEFAULT_REMOVE_PROPERTY_NAME_PREFIXES
+            = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            "jcr:reference:",
+            "jcr:path:",
+            "jcr:name:",
+            "jcr:uri:"
+    )));
+
+    /**
+     * Default list of resource names that should be ignored.
+     */
+    public static final Set<String> DEFAULT_IGNORE_RESOURCE_NAMES
+            = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            "security:acl",
+            "security:principals"
+    )));
+
+    /**
+     * List of JSON parser features activated by default.
+     */
+    public static final EnumSet<JsonParserFeature> DEFAULT_JSON_PARSER_FEATURES
+            = EnumSet.of(JsonParserFeature.COMMENTS);
+
+    private String defaultPrimaryType = DEFAULT_PRIMARY_TYPE;
+    private boolean detectCalendarValues;
+    private Set<String> ignorePropertyNames = Collections.emptySet();
+    private Set<String> ignoreResourceNames = DEFAULT_IGNORE_RESOURCE_NAMES;
+    private Set<String> removePropertyNamePrefixes = DEFAULT_REMOVE_PROPERTY_NAME_PREFIXES;
+    private EnumSet<JsonParserFeature> jsonParserFeatures = DEFAULT_JSON_PARSER_FEATURES;
+
+    /**
+     * Default "jcr:primaryType" property for resources that have no explicit value for this value.
+     * If set to null, not default type is applied.
+     *
+     * @param value Default primary type.
+     * @return this
+     */
+    public ParserOptions defaultPrimaryType(String value) {
+        this.defaultPrimaryType = value;
+        return this;
+    }
+
+    public String getDefaultPrimaryType() {
+        return defaultPrimaryType;
+    }
+
+    /**
+     * Some content formats like JSON do not contain information to identify date/time values.
+     * Instead they have to be detected by heuristics by trying to parse every string value.
+     * This mode is disabled by default.
+     *
+     * @param value Activate calendar value detection
+     * @return this
+     */
+    public ParserOptions detectCalendarValues(boolean value) {
+        this.detectCalendarValues = value;
+        return this;
+    }
+
+    public boolean isDetectCalendarValues() {
+        return detectCalendarValues;
+    }
+
+    /**
+     * Set a list of property names that should be ignored when parsing the content file.
+     *
+     * @param value List of property names
+     * @return this
+     */
+    public ParserOptions ignorePropertyNames(Set<String> value) {
+        this.ignorePropertyNames = value;
+        return this;
+    }
+
+    public Set<String> getIgnorePropertyNames() {
+        return ignorePropertyNames;
+    }
+
+    /**
+     * Set a list of resource/node names that should be ignored when parsing the content file.
+     *
+     * @param value List of resource/node names
+     * @return this
+     */
+    public ParserOptions ignoreResourceNames(Set<String> value) {
+        this.ignoreResourceNames = value;
+        return this;
+    }
+
+    public Set<String> getIgnoreResourceNames() {
+        return ignoreResourceNames;
+    }
+
+    /**
+     * Set a list of property name prefixes that should be removed automatically from the property name.
+     *
+     * @param value List of property name prefixes
+     * @return this
+     */
+    public ParserOptions removePropertyNamePrefixes(Set<String> value) {
+        this.removePropertyNamePrefixes = value;
+        return this;
+    }
+
+    public Set<String> getRemovePropertyNamePrefixes() {
+        return removePropertyNamePrefixes;
+    }
+
+    /**
+     * Set set of features the JSON parser should apply when parsing files.
+     *
+     * @param value JSON parser features
+     * @return this
+     */
+    public ParserOptions jsonParserFeatures(EnumSet<JsonParserFeature> value) {
+        this.jsonParserFeatures = value;
+        return this;
+    }
+
+    public ParserOptions jsonParserFeatures(JsonParserFeature... value) {
+        this.jsonParserFeatures = EnumSet.copyOf(Arrays.asList(value));
+        return this;
+    }
+
+    public EnumSet<JsonParserFeature> getJsonParserFeatures() {
+        return jsonParserFeatures;
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/package-info.java b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/package-info.java
new file mode 100644
index 0000000..8997911
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/main/java/org/apache/sling/contentparser/api/package-info.java
@@ -0,0 +1,22 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+@Version("1.0.0")
+package org.apache.sling.contentparser.api;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/contentparser/org-apache-sling-contentparser-api/src/test/java/org/apache/sling/contentparser/api/ParserHelperTest.java b/contentparser/org-apache-sling-contentparser-api/src/test/java/org/apache/sling/contentparser/api/ParserHelperTest.java
new file mode 100644
index 0000000..2b2da48
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-api/src/test/java/org/apache/sling/contentparser/api/ParserHelperTest.java
@@ -0,0 +1,57 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.api;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+
+public class ParserHelperTest {
+
+    @Test
+    public void parseDate() {
+        Map<String, int[]> formats = new HashMap<>();
+        formats.put("Sun Oct 31 2010 21:48:04 GMT+0100", new int[]{31, 10, 2010, 21, 48, 4, 0, 1});
+        formats.put("Sun Oct 31 2010 21:48", null);
+        formats.put("2014-09-19T21:20:26.812+02:00", new int[]{19, 9, 2014, 21, 20, 26, 812, 2});
+        formats.put("2014-09-19T21:20:26.812", null);
+        for (Map.Entry<String, int[]> entry : formats.entrySet()) {
+            int[] dateAsInts = entry.getValue();
+            Calendar calendar = ParserHelper.parseDate(entry.getKey());
+            if (dateAsInts == null) {
+                assertNull("Expected a null return value for string " + entry.getKey(), calendar);
+            } else {
+                assertEquals(dateAsInts[0], calendar.get(Calendar.DAY_OF_MONTH));
+                assertEquals(dateAsInts[1], calendar.get(Calendar.MONTH) + 1);
+                assertEquals(dateAsInts[2], calendar.get(Calendar.YEAR));
+                assertEquals(dateAsInts[3], calendar.get(Calendar.HOUR_OF_DAY));
+                assertEquals(dateAsInts[4], calendar.get(Calendar.MINUTE));
+                assertEquals(dateAsInts[5], calendar.get(Calendar.SECOND));
+                assertEquals(dateAsInts[6], calendar.get(Calendar.MILLISECOND));
+                assertEquals(dateAsInts[7], calendar.getTimeZone().getRawOffset() / 3600 / 1000);
+
+            }
+        }
+    }
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/pom.xml b/contentparser/org-apache-sling-contentparser-json/pom.xml
new file mode 100644
index 0000000..a33598b
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/pom.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.contentparser.json</artifactId>
+    <version>0.9.0-SNAPSHOT</version>
+
+    <name>Apache Sling Content Parser API</name>
+    <description>
+        Parser API Apache Sling Resource trees stored in files (e.g. JSON, FileVault XML, etc.).
+    </description>
+
+    <scm>
+        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-json.git</connection>
+        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-json.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-contentparser-json.git</url>
+      <tag>HEAD</tag>
+  </scm>
+
+    <build>
+        <plugins>
+            
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.contentparser.api</artifactId>
+            <version>0.9.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-json_1.0_spec</artifactId>
+            <version>1.0-alpha-1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.6</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.8</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.framework</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>15.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/contentparser/org-apache-sling-contentparser-json/src/main/java/org/apache/sling/contentparser/json/internal/JsonContentParser.java b/contentparser/org-apache-sling-contentparser-json/src/main/java/org/apache/sling/contentparser/json/internal/JsonContentParser.java
new file mode 100644
index 0000000..16d47e0
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/main/java/org/apache/sling/contentparser/json/internal/JsonContentParser.java
@@ -0,0 +1,212 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.stream.JsonParsingException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.sling.contentparser.api.ContentHandler;
+import org.apache.sling.contentparser.api.ContentParser;
+import org.apache.sling.contentparser.api.JsonParserFeature;
+import org.apache.sling.contentparser.api.ParseException;
+import org.apache.sling.contentparser.api.ParserHelper;
+import org.apache.sling.contentparser.api.ParserOptions;
+import org.osgi.service.component.annotations.Component;
+
+@Component(
+        property = {
+                ContentParser.SERVICE_PROPERTY_CONTENT_TYPE + "=" + ContentParser.JSON_CONTENT_TYPE
+        }
+)
+public class JsonContentParser implements ContentParser {
+
+    @Override
+    public void parse(ContentHandler handler, InputStream is, ParserOptions parserOptions) throws ParseException {
+        final boolean jsonQuoteTicks = parserOptions.getJsonParserFeatures().contains(JsonParserFeature.QUOTE_TICK);
+
+        /*
+         * Implementation note: This parser uses JsonReader instead of the (more memory-efficient)
+         * JsonParser Stream API because otherwise it would not be possible to report parent resources
+         * including all properties properly before their children.
+         */
+        final JsonReaderFactory jsonReaderFactory =
+                Json.createReaderFactory(
+                        parserOptions.getJsonParserFeatures().contains(JsonParserFeature.COMMENTS) ?
+                                new HashMap<String, Object>() {{
+                                    put("org.apache.johnzon.supports-comments", true);
+                                }} :
+                                Collections.emptyMap()
+                );
+        JsonObject jsonObject = jsonQuoteTicks ? toJsonObjectWithJsonTicks(jsonReaderFactory, is) : toJsonObject(jsonReaderFactory, is);
+        parse(handler, jsonObject, parserOptions, "/");
+    }
+
+    private JsonObject toJsonObject(JsonReaderFactory jsonReaderFactory, InputStream is) {
+        try (JsonReader reader = jsonReaderFactory.createReader(is)) {
+            return reader.readObject();
+        } catch (JsonParsingException ex) {
+            throw new ParseException("Error parsing JSON content: " + ex.getMessage(), ex);
+        }
+    }
+
+    private JsonObject toJsonObjectWithJsonTicks(JsonReaderFactory jsonReaderFactory, InputStream is) {
+        String jsonString;
+        try {
+            jsonString = IOUtils.toString(is, CharEncoding.UTF_8);
+        } catch (IOException ex) {
+            throw new ParseException("Error getting JSON string.", ex);
+        }
+
+        // convert ticks to double quotes
+        jsonString = JsonTicksConverter.tickToDoubleQuote(jsonString);
+
+        try (JsonReader reader = jsonReaderFactory.createReader(new StringReader(jsonString))) {
+            return reader.readObject();
+        } catch (JsonParsingException ex) {
+            throw new ParseException("Error parsing JSON content: " + ex.getMessage(), ex);
+        }
+    }
+
+    private void parse(ContentHandler handler, JsonObject object, ParserOptions parserOptions, String path) {
+        // parse JSON object
+        Map<String, Object> properties = new HashMap<>();
+        Map<String, JsonObject> children = new LinkedHashMap<>();
+        for (Map.Entry<String, JsonValue> entry : object.entrySet()) {
+            String childName = entry.getKey();
+            Object value = null;
+            boolean ignore = false;
+            try {
+                value = convertValue(parserOptions, entry.getValue());
+            } catch (ParseException ex) {
+                if (parserOptions.getIgnoreResourceNames().contains(childName) || parserOptions.getIgnorePropertyNames()
+                        .contains(removePrefixFromPropertyName(parserOptions.getRemovePropertyNamePrefixes(), childName))) {
+                    ignore = true;
+                } else {
+                    throw ex;
+                }
+            }
+            boolean isResource = (value instanceof JsonObject);
+            if (!ignore) {
+                if (isResource) {
+                    ignore = parserOptions.getIgnoreResourceNames().contains(childName);
+                } else {
+                    for (String prefix : parserOptions.getRemovePropertyNamePrefixes()) {
+                        if (childName.startsWith(prefix)) {
+                            childName = childName.substring(prefix.length());
+                            break;
+                        }
+
+                    }
+                    ignore = parserOptions.getIgnorePropertyNames().contains(childName);
+                }
+            }
+            if (!ignore) {
+                if (isResource) {
+                    children.put(childName, (JsonObject) value);
+                } else {
+                    properties.put(childName, value);
+                }
+            }
+        }
+        String defaultPrimaryType = parserOptions.getDefaultPrimaryType();
+        if (defaultPrimaryType != null) {
+            if (!properties.containsKey("jcr:primaryType")) {
+                properties.put("jcr:primaryType", defaultPrimaryType);
+            }
+        }
+
+        // report current JSON object
+        handler.resource(path, properties);
+
+        // parse and report children
+        for (Map.Entry<String, JsonObject> entry : children.entrySet()) {
+            String childPath = path.endsWith("/") ? path + entry.getKey() : path + "/" + entry.getKey();
+            parse(handler, entry.getValue(), parserOptions, childPath);
+        }
+    }
+
+    private Object convertValue(ParserOptions parserOptions, JsonValue value) {
+        switch (value.getValueType()) {
+            case STRING:
+                String stringValue = ((JsonString) value).getString();
+                if (parserOptions.isDetectCalendarValues()) {
+                    Calendar calendar = ParserHelper.parseDate(stringValue);
+                    if (calendar != null) {
+                        return calendar;
+                    }
+                }
+                return stringValue;
+            case NUMBER:
+                JsonNumber numberValue = (JsonNumber) value;
+                if (numberValue.isIntegral()) {
+                    return numberValue.longValue();
+                } else {
+                    return numberValue.bigDecimalValue();
+                }
+            case TRUE:
+                return true;
+            case FALSE:
+                return false;
+            case NULL:
+                return null;
+            case ARRAY:
+                JsonArray arrayValue = (JsonArray) value;
+                Object[] values = new Object[arrayValue.size()];
+                for (int i = 0; i < values.length; i++) {
+                    values[i] = convertValue(parserOptions, arrayValue.get(i));
+                }
+                return ParserHelper.convertSingleTypeArray(values);
+            case OBJECT:
+                return value;
+            default:
+                throw new ParseException("Unexpected JSON value type: " + value.getValueType());
+        }
+    }
+
+    private String removePrefixFromPropertyName(Set<String> prefixes, String propertyName) {
+        for (String prefix : prefixes) {
+            if (propertyName.startsWith(prefix)) {
+                return propertyName.substring(prefix.length());
+            }
+        }
+        return propertyName;
+    }
+
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/main/java/org/apache/sling/contentparser/json/internal/JsonTicksConverter.java b/contentparser/org-apache-sling-contentparser-json/src/main/java/org/apache/sling/contentparser/json/internal/JsonTicksConverter.java
new file mode 100644
index 0000000..df0be76
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/main/java/org/apache/sling/contentparser/json/internal/JsonTicksConverter.java
@@ -0,0 +1,106 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal;
+
+/**
+ * Converts JSON with ticks to JSON with quotes.
+ * <p>Conversions:</p>
+ * <ul>
+ * <li>Converts ticks ' to " when used as quotation marks for names or string values</li>
+ * <li>Within names or string values quoted with ticks, ticks have to be escaped with <code>\'</code>.
+ *     This escaping sign is removed on the conversion, because in JSON ticks must not be escaped.</li>
+ * <li>Within names or string values quoted with ticks, double quotes may or may not be escaped.
+ *     After the conversion they are always escaped.</li>
+ * </ul>
+ */
+final class JsonTicksConverter {
+    
+    private JsonTicksConverter() {
+        // static methods only
+    }
+    
+    static String tickToDoubleQuote(final String input) {
+        final int len = input.length();
+        final StringBuilder output = new StringBuilder(len);
+        boolean quoted = false;
+        boolean tickQuoted = false;
+        boolean escaped = false;
+        boolean comment = false;
+        char lastChar = ' ';
+        for (int i = 0; i < len; i++) {
+            char in = input.charAt(i);
+            if (quoted || tickQuoted) {
+                if (escaped) {
+                    if (in != '\'') {
+                        output.append("\\");
+                    }
+                    if (in == '\\') {
+                        output.append("\\");
+                    }
+                    escaped = false;
+                }
+                else {
+                    if (in == '"') {
+                        if (quoted) {
+                            quoted = false;
+                        }
+                        else if (tickQuoted) {
+                            output.append("\\");
+                        }
+                    }
+                    else if (in == '\'') {
+                        if (tickQuoted) {
+                            in = '"';
+                            tickQuoted = false;
+                        }
+                    }
+                    else if (in == '\\') {
+                        escaped = true;
+                    }
+                }
+            }
+            else {
+                if (comment) {
+                    if (lastChar == '*' && in == '/') {
+                        comment = false;
+                    }
+                }
+                else {
+                    if (lastChar == '/' && in == '*') {
+                        comment = true;
+                    }
+                    else if (in == '\'') {
+                        in = '"';
+                        tickQuoted = true;
+                    }
+                    else if (in == '"') {
+                        quoted = true;
+                    }
+                }
+            }
+            if (in == '\\') {
+                continue;
+            }
+            output.append(in);
+            lastChar = in;
+        }
+        return output.toString();
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/JsonContentParserTest.java b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/JsonContentParserTest.java
new file mode 100644
index 0000000..3099ae8
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/JsonContentParserTest.java
@@ -0,0 +1,186 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.sling.contentparser.api.ContentParser;
+import org.apache.sling.contentparser.api.JsonParserFeature;
+import org.apache.sling.contentparser.api.ParseException;
+import org.apache.sling.contentparser.api.ParserOptions;
+import org.apache.sling.contentparser.json.internal.mapsupport.ContentElement;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import static junit.framework.TestCase.assertNull;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class JsonContentParserTest {
+
+    private File file;
+    private ContentParser contentParser;
+
+    @Before
+    public void setUp() {
+        file = new File("src/test/resources/content-test/content.json");
+        contentParser = new JsonContentParser();
+    }
+
+    @Test
+    public void testPageJcrPrimaryType() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, file);
+
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+    }
+
+    @Test
+    public void testDataTypes() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, file);
+
+        Map<String, Object> props = content.getChild("toolbar/profiles/jcr:content").getProperties();
+        assertEquals(true, props.get("hideInNav"));
+
+        assertEquals(1234567890123L, props.get("longProp"));
+        assertEquals(new BigDecimal("1.2345"), props.get("decimalProp"));
+        assertEquals(true, props.get("booleanProp"));
+
+        assertArrayEquals(new Long[]{1234567890123L, 55L}, (Long[]) props.get("longPropMulti"));
+        assertArrayEquals(new BigDecimal[]{new BigDecimal("1.2345"), new BigDecimal("1.1")}, (BigDecimal[]) props.get("decimalPropMulti"));
+        assertArrayEquals(new Boolean[]{true, false}, (Boolean[]) props.get("booleanPropMulti"));
+    }
+
+    @Test
+    public void testContentProperties() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, file);
+
+        Map<String, Object> props = content.getChild("jcr:content/header").getProperties();
+        assertEquals("/content/dam/sample/header.png", props.get("imageReference"));
+    }
+
+    @Test
+    public void testCalendar() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, new ParserOptions().detectCalendarValues(true), file);
+
+        Map<String, Object> props = content.getChild("jcr:content").getProperties();
+
+        Calendar calendar = (Calendar) props.get("app:lastModified");
+        assertNotNull(calendar);
+
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT+2"));
+
+        assertEquals(2014, calendar.get(Calendar.YEAR));
+        assertEquals(4, calendar.get(Calendar.MONTH) + 1);
+        assertEquals(22, calendar.get(Calendar.DAY_OF_MONTH));
+
+        assertEquals(15, calendar.get(Calendar.HOUR_OF_DAY));
+        assertEquals(11, calendar.get(Calendar.MINUTE));
+        assertEquals(24, calendar.get(Calendar.SECOND));
+    }
+
+    @Test
+    public void testIso8601Calendar() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, new ParserOptions().detectCalendarValues(true), file);
+
+        Map<String, Object> props = content.getChild("jcr:content").getProperties();
+
+        Calendar calendar = (Calendar) props.get("dateISO8601String");
+        assertNotNull(calendar);
+
+        assertEquals(2014, calendar.get(Calendar.YEAR));
+        assertEquals(4, calendar.get(Calendar.MONTH) + 1);
+        assertEquals(22, calendar.get(Calendar.DAY_OF_MONTH));
+
+        assertEquals(15, calendar.get(Calendar.HOUR_OF_DAY));
+        assertEquals(11, calendar.get(Calendar.MINUTE));
+        assertEquals(24, calendar.get(Calendar.SECOND));
+    }
+
+    @Test
+    public void testUTF8Chars() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, file);
+
+        Map<String, Object> props = content.getChild("jcr:content").getProperties();
+
+        assertEquals("äöü߀", props.get("utf8Property"));
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseInvalidJson() throws Exception {
+        file = new File("src/test/resources/invalid-test/invalid.json");
+        ContentElement content = TestUtils.parse(contentParser, file);
+        assertNull(content);
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseInvalidJsonWithObjectList() throws Exception {
+        file = new File("src/test/resources/invalid-test/contentWithObjectList.json");
+        ContentElement content = TestUtils.parse(contentParser, file);
+        assertNull(content);
+    }
+
+    @Test
+    public void testIgnoreResourcesProperties() throws Exception {
+        ContentElement content = TestUtils.parse(
+                contentParser,
+                new ParserOptions().ignoreResourceNames(ImmutableSet.of("header", "newslist", "security:acl", "security:principals"))
+                        .ignorePropertyNames(ImmutableSet.of("jcr:title")), file);
+        ContentElement child = content.getChild("jcr:content");
+
+        assertEquals("Sample Homepage", child.getProperties().get("pageTitle"));
+        assertNull(child.getProperties().get("jcr:title"));
+
+        assertNull(child.getChildren().get("header"));
+        assertNull(child.getChildren().get("newslist"));
+        assertNotNull(child.getChildren().get("lead"));
+
+        assertEquals("abc", child.getProperties().get("refpro1"));
+        assertEquals("def", child.getProperties().get("pathprop1"));
+    }
+
+    @Test
+    public void testGetChild() throws Exception {
+        ContentElement content = TestUtils.parse(contentParser, file);
+        assertNull(content.getName());
+
+        ContentElement deepChild = content.getChild("jcr:content/par/image/file/jcr:content");
+        assertEquals("jcr:content", deepChild.getName());
+        assertEquals("nt:resource", deepChild.getProperties().get("jcr:primaryType"));
+
+        ContentElement invalidChild = content.getChild("non/existing/path");
+        assertNull(invalidChild);
+
+        invalidChild = content.getChild("/jcr:content");
+        assertNull(invalidChild);
+    }
+
+    @Test(expected = ParseException.class)
+    public void testFailsWithoutCommentsEnabled() throws Exception {
+        TestUtils.parse(contentParser, new ParserOptions().jsonParserFeatures(EnumSet.noneOf(JsonParserFeature.class)), file);
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/TestUtils.java b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/TestUtils.java
new file mode 100644
index 0000000..0d81e36
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/TestUtils.java
@@ -0,0 +1,60 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sling.contentparser.api.ContentParser;
+import org.apache.sling.contentparser.api.ParserOptions;
+import org.apache.sling.contentparser.json.internal.mapsupport.ContentElement;
+import org.apache.sling.contentparser.json.internal.mapsupport.ContentElementHandler;
+
+public final class TestUtils {
+    
+    private TestUtils() {
+        // static methods only
+    }
+
+    static ContentElement parse(ContentParser contentParser, File file) throws IOException {
+        return parse(contentParser, new ParserOptions(), file);
+    }
+
+    static ContentElement parse(ContentParser contentParser, ParserOptions parserOptions, File file) throws IOException {
+        try (FileInputStream fis = new FileInputStream(file);
+             BufferedInputStream bis = new BufferedInputStream(fis)) {
+            ContentElementHandler handler = new ContentElementHandler();
+            contentParser.parse(handler, bis, parserOptions);
+            return handler.getRoot();
+        }
+    }
+    
+    static ContentElement parse(ContentParser contentParser, String jsonContent) throws IOException {
+        try (ByteArrayInputStream is = new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8))) {
+            ContentElementHandler handler = new ContentElementHandler();
+            contentParser.parse(handler, is, new ParserOptions());
+            return handler.getRoot();
+        }
+    }
+    
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElement.java b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElement.java
new file mode 100644
index 0000000..0a4457e
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElement.java
@@ -0,0 +1,52 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal.mapsupport;
+
+import java.util.Map;
+
+/**
+ * Represents a resource or node in the content hierarchy.
+ */
+public interface ContentElement {
+
+    /**
+     * @return Resource name. The root resource has no name (null).
+     */
+    String getName();
+    
+    /**
+     * Properties of this resource.
+     * @return Properties (keys, values)
+     */
+    Map<String, Object> getProperties();
+    
+    /**
+     * Get children of current resource. The Map preserves the ordering of children.
+     * @return Children (child names, child objects)
+     */
+    Map<String, ContentElement> getChildren();
+    
+    /**
+     * Get child or descendant
+     * @param path Relative path to address child or one of it's descendants (use "/" as hierarchy separator).
+     * @return Child or null if no child found with this path
+     */
+    ContentElement getChild(String path);
+    
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElementHandler.java b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElementHandler.java
new file mode 100644
index 0000000..bc21ec0
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElementHandler.java
@@ -0,0 +1,69 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal.mapsupport;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.contentparser.api.ContentHandler;
+
+/**
+ * {@link ContentHandler} implementation that produces a tree of {@link ContentElement} items.
+ */
+public class ContentElementHandler implements ContentHandler {
+    
+    private ContentElement root;
+    private Pattern PATH_PATTERN = Pattern.compile("^((/[^/]+)*)(/([^/]+))$"); 
+
+    @Override
+    public void resource(String path, Map<String, Object> properties) {
+        if (StringUtils.equals(path, "/")) {
+            root = new ContentElementImpl(null, properties);
+        }
+        else {
+            if (root == null) {
+                throw new RuntimeException("Root resource not set.");
+            }
+            Matcher matcher = PATH_PATTERN.matcher(path);
+            if (!matcher.matches()) {
+                throw new RuntimeException("Unexpected path:" + path);
+            }
+            String relativeParentPath = StringUtils.stripStart(matcher.group(1), "/");
+            String name = matcher.group(4);
+            ContentElement parent;
+            if (StringUtils.isEmpty(relativeParentPath)) {
+                parent = root;
+            }
+            else {
+                parent = root.getChild(relativeParentPath);
+            }
+            if (parent == null) {
+                throw new RuntimeException("Parent '" + relativeParentPath + "' does not exist.");
+            }
+            parent.getChildren().put(name, new ContentElementImpl(name, properties));
+        }
+    }
+    
+    public ContentElement getRoot() {
+        return root;
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElementImpl.java b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElementImpl.java
new file mode 100644
index 0000000..ddbf9bd
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/java/org/apache/sling/contentparser/json/internal/mapsupport/ContentElementImpl.java
@@ -0,0 +1,68 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.json.internal.mapsupport;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+final class ContentElementImpl implements ContentElement {
+    
+    private final String name;
+    private final Map<String, Object> properties;
+    private final Map<String, ContentElement> children = new LinkedHashMap<>();
+    
+    public ContentElementImpl(String name, Map<String, Object> properties) {
+        this.name = name;
+        this.properties = properties;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public Map<String, ContentElement> getChildren() {
+        return children;
+    }
+
+    @Override
+    public ContentElement getChild(String path) {
+        String name = StringUtils.substringBefore(path, "/");
+        ContentElement child = children.get(name);
+        if (child == null) {
+          return null;
+        }
+        String remainingPath = StringUtils.substringAfter(path, "/");
+        if (StringUtils.isEmpty(remainingPath)) {
+          return child;
+        }
+        else {
+          return child.getChild(remainingPath);
+        }
+    }
+
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/resources/content-test/content.json b/contentparser/org-apache-sling-contentparser-json/src/test/resources/content-test/content.json
new file mode 100644
index 0000000..8890044
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/resources/content-test/content.json
@@ -0,0 +1,274 @@
+/* Comment example */
+{
+  "jcr:primaryType": "app:Page",
+  "jcr:createdBy": "admin",
+  "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+  /* Comment example */
+  "jcr:content": {
+    "jcr:primaryType": "app:PageContent",  /* Comment example */
+    "jcr:createdBy": "admin",
+    "jcr:title": "English",
+    "app:template": "sample/templates/homepage",
+    "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+    "app:lastModified": "Tue Apr 22 2014 15:11:24 GMT+0200",
+    "dateISO8601String": "2014-04-22T15:11:24.000+02:00",
+    "pageTitle": "Sample Homepage",
+    "sling:resourceType": "sample/components/homepage",
+    "sling:resourceSuperType": "sample/components/supertype",
+    "app:designPath": "/etc/designs/sample",
+    "app:lastModifiedBy": "admin",
+    "utf8Property": "äöü߀",
+    "jcr:reference:refpro1": "abc",
+    "jcr:path:pathprop1": "def",
+    /* should be ignored */
+    "security:acl": [
+        { "principal": "TestGroup1", "granted": ["jcr:read","jcr:write"] },
+        { "principal": "TestUser1", "granted": ["jcr:read"], "denied": ["jcr:write"] }
+    ],
+    /* should be ignored */
+    "security:principals": [
+        { "name": "TestUser1", "password": "mypassword", "extraProp1": "extraProp1Value" },
+        { "name": "TestGroup1", "isgroup": "true", "members": ["TestUser1"], "extraProp1": "extraProp1Value" }
+    ],
+    "par": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "colctrl": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "layout": "2;colctrl-lt0",
+        "jcr:created": "Mon Aug 23 2010 22:02:24 GMT+0200",
+        "jcr:lastModified": "Mon Aug 23 2010 22:02:35 GMT+0200",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      },
+      "image": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "fileReference": "/content/dam/sample/portraits/jane_doe.jpg",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:03:39 GMT+0200",
+        "width": "340",
+        "jcr:lastModified": "Sun Oct 31 2010 21:39:50 GMT+0100",
+        "sling:resourceType": "foundation/components/image",
+        "file": {
+          "jcr:primaryType": "nt:file",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+          "jcr:content": {
+            "jcr:primaryType": "nt:resource",
+            "jcr:lastModifiedBy": "anonymous",
+            "jcr:mimeType": "image/jpeg",
+            "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200",
+            ":jcr:data": 24377,
+            "jcr:uuid": "eda76d00-b2cd-4b59-878f-c33f71ceaddc"
+          }
+        }
+      },
+      "title_1": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Strategic Consulting",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:12:08 GMT+0200",
+        "jcr:lastModified": "Wed Oct 27 2010 21:33:24 GMT+0200",
+        "sling:resourceType": "sample/components/title"
+      },
+      "text_1": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Sun Oct 31 2010 21:48:04 GMT+0100",
+        "text": "<p><span class=\"Apple-style-span\" style=\"font-size: 12px;\">In&nbsp;today's competitive market, organizations can face several key geometric challenges:<\/span><\/p>\n<ul>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Polyhedral Sectioning<\/span><\/li>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Triangulation&nbsp;<\/span><\/li>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Trigonometric Calculation<\/span><\ [...]
+        "jcr:lastModified": "Sun Oct 31 2010 21:49:06 GMT+0100",
+        "sling:resourceType": "foundation/components/text",
+        "textIsRich": "true"
+      },
+      "col_break12825937554040": {
+        "jcr:primaryType": "nt:unstructured",
+        "controlType": "break",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      },
+      "image_0": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "fileReference": "/content/dam/sample/offices/clean_room.jpg",
+        "height": "226",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:04:46 GMT+0200",
+        "jcr:lastModified": "Fri Nov 05 2010 10:38:15 GMT+0100",
+        "sling:resourceType": "foundation/components/image",
+        "imageRotate": "0",
+        "file": {
+          "jcr:primaryType": "nt:file",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+          "jcr:content": {
+            "jcr:primaryType": "nt:resource",
+            "jcr:lastModifiedBy": "anonymous",
+            "jcr:mimeType": "image/jpeg",
+            "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200",
+            ":jcr:data": 21142,
+            "jcr:uuid": "6139077f-191f-4337-aaef-55456ebe6784"
+          }
+        }
+      },
+      "title_2": {
+        "jcr:createdBy": "admin",
+        "jcr:title": "Shape Technology",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:12:13 GMT+0200",
+        "jcr:lastModified": "Tue Oct 26 2010 21:16:29 GMT+0200",
+        "sling:resourceType": "sample/components/title"
+      },
+      "text_0": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:16:30 GMT+0200",
+        "text": "<p>The Sample investment in R&amp;D has done more than solidify our industry leadership role, we have now outpaced our competitors to such an extent that we are in an altogether new space.<\/p>\n<p>This is why our high quality polygons and polyhedra provide the only turnkey solutions across the whole range of euclidean geometry. And our mathematicians are working on the next generation of fractal curves to bring you shapes that are unthinkable today.<\/p>\n<p><\/p>\n<p>< [...]
+        "jcr:lastModified": "Mon Nov 08 2010 20:39:00 GMT+0100",
+        "sling:resourceType": "foundation/components/text",
+        "textIsRich": "true"
+      },
+      "col_end12825937444810": {
+        "jcr:primaryType": "nt:unstructured",
+        "controlType": "end",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      }
+    },
+    "header": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:title": "trust our experience\r\nto manage your business",
+      "imageReference": "/content/dam/sample/header.png",
+      "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more",
+      "sling:resourceType": "sample/components/header"
+    },
+    "newslist": {
+      "jcr:primaryType": "nt:unstructured",
+      "headline": "trust our experience\nto manage your business",
+      "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more",
+      "sling:resourceType": "sample/components/listchildren",
+      "listroot": "/content/sample/en/about/news"
+    },
+    "lead": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:title": "World Leader in Applied Geometry ",
+      "jcr:lastModifiedBy": "admin",
+      "text": "Lead Text",
+      "title": "Lead Title",
+      "jcr:description": "Sample has been selling and servicing shapes for over 2000 years. From our beginnings as a small vendor of squares and rectangles we have grown our business into a leading global provider of platonic solids and fractals. Join us as we lead geometry into the future.",
+      "jcr:lastModified": "Wed Jan 19 2011 14:35:29 GMT+0100",
+      "sling:resourceType": "sample/components/lead",
+      "app:annotations": {"jcr:primaryType": "nt:unstructured"}
+    },
+    "image": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:lastModifiedBy": "admin",
+      "jcr:lastModified": "Wed Oct 27 2010 21:30:59 GMT+0200",
+      "imageRotate": "0"
+    },
+    "carousel": {
+      "jcr:primaryType": "nt:unstructured",
+      "playSpeed": "6000",
+      "jcr:lastModifiedBy": "admin",
+      "pages": [
+        "/content/sample/en/events/techsummit",
+        "/content/sample/en/events/userconf",
+        "/content/sample/en/events/shapecon",
+        "/content/sample/en/events/dsc"
+      ],
+      "jcr:lastModified": "Tue Oct 05 2010 14:14:27 GMT+0200",
+      "transTime": "1000",
+      "sling:resourceType": "foundation/components/carousel",
+      "listFrom": "static"
+    },
+    "rightpar": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "teaser": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Tue Jan 25 2011 11:30:09 GMT+0100",
+        "campaignpath": "/content/campaigns/sample",
+        "jcr:lastModified": "Wed Feb 02 2011 08:40:30 GMT+0100",
+        "sling:resourceType": "personalization/components/teaser"
+      }
+    }
+  },
+  "toolbar": {
+    "jcr:primaryType": "app:Page",
+    "jcr:createdBy": "admin",
+    "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+    "jcr:content": {
+      "jcr:primaryType": "app:PageContent",
+      "subtitle": "Contains the toolbar",
+      "jcr:createdBy": "admin",
+      "jcr:title": "Toolbar",
+      "app:template": "sample/templates/contentpage",
+      "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+      "app:lastModified": "Wed Aug 25 2010 22:51:02 GMT+0200",
+      "hideInNav": "true",
+      "sling:resourceType": "sample/components/contentpage",
+      "app:lastModifiedBy": "admin",
+      "par": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "foundation/components/parsys"
+      },
+      "rightpar": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "foundation/components/iparsys",
+        "iparsys_fake_par": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/iparsys/par"
+        }
+      }
+    },
+    "profiles": {
+      "jcr:primaryType": "app:Page",
+      "jcr:createdBy": "admin",
+      "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+      "jcr:content": {
+        "jcr:primaryType": "app:PageContent",
+        "jcr:mixinTypes": ["type1","type2"],
+        "jcr:createdBy": "admin",
+        "jcr:title": "Profiles",
+        "app:template": "sample/templates/contentpage",
+        "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+        "app:lastModified": "Thu Nov 05 2009 20:27:13 GMT+0100",
+        "hideInNav": true,
+        "sling:resourceType": "sample/components/contentpage",
+        "app:lastModifiedBy": "admin",
+        "longProp": 1234567890123,
+        "decimalProp": 1.2345,
+        "booleanProp": true,
+        "longPropMulti": [1234567890123,55],
+        "decimalPropMulti": [1.2345,1.1],
+        "booleanPropMulti": [true,false],
+        "stringPropMulti": ["aa","bb","cc"],
+        "par": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/parsys",
+          "textimage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "foundation/components/textimage"
+          },
+          "mygadgets": {
+            "jcr:primaryType": "nt:unstructured",
+            "gadgets": "http://customer.meteogroup.de/meteogroup/gadgets/wetter24.xml\nhttp://germanweatherradar.googlecode.com/svn/trunk/german-weather-radar.xml\nhttp://www.digitalpowered.info/gadget/ski.pictures.xml\nhttp://www.canbuffi.de/gadgets/clock/clock.xml",
+            "sling:resourceType": "personalization/components/mygadgets"
+          }
+        },
+        "rightpar": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/iparsys",
+          "iparsys_fake_par": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "foundation/components/iparsys/par"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/resources/invalid-test/contentWithObjectList.json b/contentparser/org-apache-sling-contentparser-json/src/test/resources/invalid-test/contentWithObjectList.json
new file mode 100644
index 0000000..e4527a0
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/resources/invalid-test/contentWithObjectList.json
@@ -0,0 +1,14 @@
+{
+  "prop1": "value1",
+  "childObject": {
+    "prop2": "value2"
+  },
+  "childObjectList": [
+    {
+      "prop1": "value1"
+    },
+    {
+      "prop2": "value2"
+    }
+  ]
+}
diff --git a/contentparser/org-apache-sling-contentparser-json/src/test/resources/invalid-test/invalid.json b/contentparser/org-apache-sling-contentparser-json/src/test/resources/invalid-test/invalid.json
new file mode 100644
index 0000000..59fc86c
--- /dev/null
+++ b/contentparser/org-apache-sling-contentparser-json/src/test/resources/invalid-test/invalid.json
@@ -0,0 +1 @@
+This is invalid json.