You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2020/04/13 08:27:00 UTC

[felix-dev] branch master updated: Add new module for JSON configuration handling

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

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 0091c7d  Add new module for JSON configuration handling
0091c7d is described below

commit 0091c7d86c3bd1fdfb039fac68368f256f741bcd
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Apr 13 10:26:49 2020 +0200

    Add new module for JSON configuration handling
---
 cm.json/pom.xml                                    | 125 ++++
 .../apache/felix/cm/json/ConfigurationReader.java  | 150 +++++
 .../felix/cm/json/ConfigurationResource.java       |  51 ++
 .../apache/felix/cm/json/ConfigurationWriter.java  |  77 +++
 .../org/apache/felix/cm/json/Configurations.java   | 123 ++++
 .../cm/json/impl/ConfigurationReaderImpl.java      | 310 ++++++++++
 .../cm/json/impl/ConfigurationWriterImpl.java      | 111 ++++
 .../org/apache/felix/cm/json/impl/JsonSupport.java | 305 ++++++++++
 .../felix/cm/json/impl/OrderedDictionary.java      | 145 +++++
 .../apache/felix/cm/json/impl/TypeConverter.java   | 270 +++++++++
 .../org/apache/felix/cm/json/package-info.java     |  22 +
 .../cm/json/impl/ConfigurationReaderImplTest.java  |  92 +++
 .../apache/felix/cm/json/impl/JsonSupportTest.java | 193 ++++++
 .../felix/cm/json/impl/TypeConverterTest.java      | 650 +++++++++++++++++++++
 cm.json/src/test/resources/configs/bundle.json     |   8 +
 cm.json/src/test/resources/configs/resource.json   |  10 +
 cm.json/src/test/resources/configs/single.json     |   3 +
 17 files changed, 2645 insertions(+)

diff --git a/cm.json/pom.xml b/cm.json/pom.xml
new file mode 100644
index 0000000..9d75a1f
--- /dev/null
+++ b/cm.json/pom.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>6</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.felix.cm.json</artifactId>
+    <packaging>bundle</packaging>
+
+    <name>Apache Felix Configuration Json</name>
+    <description>
+        Support for OSGi configurations specified in JSON documents.
+    </description>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <scm>
+        <connection>scm:git:https://github.com/apache/felix-dev/tree/master/cm.json</connection>
+        <developerConnection>scm:git:https://github.com/apache/felix-dev/tree/master/cm.json</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git;f=cm.json</url>
+    </scm>
+
+    <properties>
+        <felix.java.version>8</felix.java.version>
+    </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>3.5.0</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Category>osgi</Bundle-Category>
+                        <Bundle-SymbolicName>
+                            ${project.artifactId}
+                        </Bundle-SymbolicName>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes combine.children="append">
+                        <!-- Exclude all JSON files -->
+                        <exclude>**/*.json</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+          </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.annotation</artifactId>
+            <version>6.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>6.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.configurator</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.converter</artifactId>
+            <version>1.0.12</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-json_1.1_spec</artifactId>
+            <version>1.2</version>
+            <scope>provided</scope>
+        </dependency>
+       <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.util.function</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
+         <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+            <version>1.2.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationReader.java b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationReader.java
new file mode 100644
index 0000000..7bf5b96
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationReader.java
@@ -0,0 +1,150 @@
+/*
+ * 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.felix.cm.json;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.json.JsonObject;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A configuration reader can read configuration resources as defined in
+ * {@link https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html}.
+ * A reader can be obtained through a {@link Builder}.
+ */
+@ProviderType
+public interface ConfigurationReader {
+
+    /**
+     * Read a single configuration from the provided reader. The reader is not
+     * closed. {@link #getIgnoredErrors()} can be used after reading if any ignored
+     * errors occurred during processing the configuration.
+     *
+     * @return The read configuration.
+     * @throws IOException If reading fails
+     */
+    Hashtable<String, Object> readConfiguration() throws IOException;
+
+    /**
+     * Read a configuration resource from the provided reader. The reader is not
+     * closed. {@link #getIgnoredErrors()} can be used after reading if any ignored
+     * errors occurred during processing the resource.
+     *
+     * @return The read configurations.
+     * @throws IOException If reading fails
+     */
+    ConfigurationResource readConfigurationResource() throws IOException;
+
+    /**
+     * After reading a configuration (resource) this method will return a list of
+     * ignored errors.
+     *
+     * @return List of ignored errors, might be empty.
+     */
+    List<String> getIgnoredErrors();
+
+    /**
+     * Builder to create a reader. A builder can be obtained using
+     * {@link Configurations#buildReader()}
+     */
+    public interface Builder {
+
+        /**
+         * By default a configuration resource is not treated as a bundle resource.
+         * Additional rules apply for a resource outside of a bundle.
+         *
+         * @return This builder
+         */
+        Builder verifyAsBundleResource();
+
+        /**
+         * Set an identifier. If set all ignored error messages and most exceptions will
+         * contain this identifier.
+         *
+         * @param identifier An identifier for the resource
+         * @return This builder
+         */
+        Builder withIdentifier(String identifier);
+
+        /**
+         * Set the binary handler.
+         * If no binary handler is set and a binary property is encountered
+         * the reading of the configuration resource fails.
+         *
+         * @param handler The binary handler
+         * @return This builder
+         */
+        Builder withBinaryHandler(BinaryHandler handler);
+
+        /**
+         * Set the configurator property handler.
+         * If no handler is set and a configurator property is encountered
+         * it is added as is to the returned configuration dictionary.
+         *
+         * @param handler The binary handler
+         * @return This builder
+         */
+        Builder withConfiguratorPropertyHandler(ConfiguratorPropertyHandler handler);
+
+        /**
+         * Build the configuration reader for the provided reader.
+         *
+         * @param reader The reader for the JSON
+         * @return The configuration reader
+         */
+        ConfigurationReader build(Reader reader);
+
+        /**
+         * Build the configuration reader for the JSON object.
+         *
+         * @param object The JSON object
+         * @return The configuration reader
+         */
+        ConfigurationReader build(JsonObject object);
+    }
+
+    /**
+     * Handler for binary properties
+     */
+    @FunctionalInterface
+    public interface BinaryHandler {
+
+        /**
+         * Provide the actual value for a binary property
+         * @param pid The pid of the configuration
+         * @param key The key of the binary property
+         * @param value The binary property value
+         * @return The actual value or {@code null} on failure
+         */
+        String handleBinaryValue(String pid, String key, String value);
+    }
+
+    /**
+     * Handler for configurator properties
+     */
+    @FunctionalInterface
+    public interface ConfiguratorPropertyHandler {
+
+        void handleConfiguratorProperty(String pid, String key, Object value);
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationResource.java b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationResource.java
new file mode 100644
index 0000000..ac151b3
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationResource.java
@@ -0,0 +1,51 @@
+/*
+ * 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.felix.cm.json;
+
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A configuration resource holds a set of configurations
+ */
+public final class ConfigurationResource {
+
+    public static final String CONFIGURATOR_PROPERTY_PREFIX = ":configurator:";
+
+    private final Map<String, Hashtable<String, Object>> configurations = new LinkedHashMap<>();
+
+    private final Map<String, Object> properties = new LinkedHashMap<>();
+
+    /**
+     * The set of configurations mapped by identifier.
+     * @return The configurations.
+     */
+    public Map<String, Hashtable<String, Object>> getConfigurations() {
+        return this.configurations;
+    }
+
+    /**
+     * The properties of the resource
+     * @return A map pf properties
+     */
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationWriter.java b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationWriter.java
new file mode 100644
index 0000000..0128a88
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/ConfigurationWriter.java
@@ -0,0 +1,77 @@
+/*
+ * 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.felix.cm.json;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Dictionary;
+
+import javax.json.stream.JsonGenerator;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A configuration writer can write configuration resources as defined in
+ * {@link https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html}.
+ * A writer can be obtained through a {@link Builder}.
+ */
+@ProviderType
+public interface ConfigurationWriter {
+
+    /**
+     * Write a single configuration to the provided writer. The writer is not
+     * closed.
+     *
+     * @param properties The configuration
+     * @throws IOException If writing fails
+     */
+    void writeConfiguration(Dictionary<String, Object> properties) throws IOException;
+
+    /**
+     * Write a configuration resource to the provided writer. The writer is not
+     * closed.
+     *
+     * @param resource The configuration resource
+     * @throws IOException If writing fails
+     */
+    void writeConfigurationResource(ConfigurationResource resource) throws IOException;
+
+    /**
+     * Builder to create a writer. A builder can be obtained using
+     * {@link Configurations#buildWriter()}
+     */
+    public interface Builder {
+
+        /**
+         * Build the configuration writer for the provided writer.
+         *
+         * @param writer The writer for the JSON
+         * @return The configuration writer
+         */
+        ConfigurationWriter build(Writer writer);
+
+        /**
+         * Build the configuration writer for the provided JSON generator.
+         *
+         * @param generator The JSON generator
+         * @return The configuration writer
+         */
+        ConfigurationWriter build(JsonGenerator generator);
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/Configurations.java b/cm.json/src/main/java/org/apache/felix/cm/json/Configurations.java
new file mode 100644
index 0000000..8bdd1bd
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/Configurations.java
@@ -0,0 +1,123 @@
+/*
+ * 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.felix.cm.json;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Hashtable;
+
+import javax.json.JsonValue;
+
+import org.apache.felix.cm.json.impl.ConfigurationReaderImpl;
+import org.apache.felix.cm.json.impl.ConfigurationWriterImpl;
+import org.apache.felix.cm.json.impl.JsonSupport;
+import org.apache.felix.cm.json.impl.OrderedDictionary;
+
+/**
+ * Factory class for JSON and configuration support
+ */
+public class Configurations {
+
+
+    private Configurations() {
+        // Do not instantiate this factory class
+    }
+
+    /**
+     * Get a builder to create a configuration reader
+     *
+     * @return A configuration reader builder
+     */
+    public static ConfigurationReader.Builder buildReader() {
+        return new ConfigurationReaderImpl();
+    }
+
+    /**
+     * Get a builder to create a configuration writer
+     *
+     * @return A configuration writer builder
+     */
+    public static ConfigurationWriter.Builder buildWriter() {
+        return new ConfigurationWriterImpl();
+    }
+
+    /**
+     * Create a reader handling comments in JSON. The reader tries to leave the original
+     * structure of the document untouched, meaning comments will be replace with spaces.
+     * Closing this reader will close the underlying reader.
+     *
+     * @param reader Reader for the JSON
+     * @return Reader for the pure JSON
+     * @throws IOException If reading fails.
+     */
+    public static Reader jsonCommentAwareReader(final Reader reader) throws IOException {
+        return JsonSupport.createCommentRemovingReader(reader);
+    }
+
+    /**
+     * Create a new map for configuration properties The returned map keeps the
+     * order of properties added.
+     *
+     * @return A new map
+     */
+    public static Hashtable<String, Object> newConfiguration() {
+        return new OrderedDictionary();
+    }
+
+    /**
+     * Convert a json value into an object. The resulting object is
+     * <ul>
+     * <li>{@code null} : if the provided value is {@code null} or
+     * {@code ValueType#NULL}
+     * <li>A Boolean : if the json value is a boolean
+     * <li>A Long : if the json value contains an integral number
+     * <li>A Double : if the json value contains a non integral number
+     * <li>A String : for any other provided non array value
+     * <li>A Boolean[] : if the provided value is an array of booleans
+     * <li>A Long[] : if the provided value is an array of integral numbers
+     * <li>A Double[] : if the provided value is an array of non integral numbers
+     * <li>A String[] : for any other provided array value
+     * </ul>
+     *
+     * @param value The json value
+     * @return The object or {@code null} if the provided value is {@code null} or
+     *         {@code ValueType#NULL}
+     */
+    public static Object convertToObject(final JsonValue value) {
+        return JsonSupport.convertToObject(value);
+    }
+
+    /**
+     * Convert an object to a JSON value. The resulting value is:
+     * <ul>
+     * <li>{@code JsonValue.NULL} : if the provided value is {@code null}
+     * <li>{@code JsonValue.TRUE} or {@code JsonValue.FALSE} : if the provided value is Boolean.
+     * <li>A JSON value of type {@code JsonValue.ValueType#NUMBER} : if the value is a Integer, Long, Float, Double, Byte, Short
+     * <li>{@code JsonValue.ValueType#OBJECT} : if the value is a map
+     * <li>{@code JsonValue.ValueType#ARRAY} : if the value is an array or a collection
+     * <li>{@code JsonValue.ValueType#STRING} : otherwise, calling {@code toString()} on the value.
+     * <li>
+     * </ul>
+     * @param value The object
+     * @return The JSON value.
+     */
+    public static JsonValue convertToJsonValue(final Object value) {
+        return JsonSupport.convertToJson(value);
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationReaderImpl.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationReaderImpl.java
new file mode 100644
index 0000000..b3b73b2
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationReaderImpl.java
@@ -0,0 +1,310 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
+
+import org.apache.felix.cm.json.ConfigurationReader;
+import org.apache.felix.cm.json.ConfigurationResource;
+import org.apache.felix.cm.json.Configurations;
+import org.osgi.service.configurator.ConfiguratorConstants;
+import org.osgi.util.converter.Converters;
+
+public class ConfigurationReaderImpl
+        implements ConfigurationReader, ConfigurationReader.Builder {
+
+    private boolean closed = false;
+
+    private Reader reader;
+
+    private JsonObject jsonObject;
+
+    private boolean verifyAsBundleResource = false;
+
+    private String identifier;
+
+    private final List<String> errors = new ArrayList<>();
+
+    private BinaryHandler binaryHandler;
+
+    private ConfiguratorPropertyHandler propertyHandler;
+
+    @Override
+    public Builder verifyAsBundleResource() {
+        this.verifyAsBundleResource = true;
+        return this;
+    }
+
+    @Override
+    public Builder withIdentifier(final String value) {
+        this.identifier = value;
+        return this;
+    }
+
+    @Override
+    public Builder withBinaryHandler(final BinaryHandler handler) {
+        this.binaryHandler = handler;
+        return this;
+    }
+
+    @Override
+    public Builder withConfiguratorPropertyHandler(final ConfiguratorPropertyHandler handler) {
+        this.propertyHandler = handler;
+        return this;
+    }
+
+    @Override
+    public ConfigurationReader build(final Reader reader) {
+        this.reader = reader;
+        return this;
+    }
+
+    @Override
+    public ConfigurationReader build(final JsonObject object) {
+        this.jsonObject = object;
+        return this;
+    }
+
+    @Override
+    public List<String> getIgnoredErrors() {
+        return this.errors;
+    }
+
+    private void checkClosed() throws IOException {
+        if (this.closed) {
+            throwIOException("Reader already closed");
+        }
+        closed = true;
+    }
+
+    @Override
+    public Hashtable<String, Object> readConfiguration() throws IOException {
+        checkClosed();
+        if (this.reader != null) {
+            this.jsonObject = JsonSupport.parseJson(this.identifier, this.reader);
+        }
+        return readSingleConfiguration("<configuration>", this.jsonObject);
+    }
+
+    @Override
+    public ConfigurationResource readConfigurationResource() throws IOException {
+        checkClosed();
+        if (this.reader != null) {
+            this.jsonObject = JsonSupport.parseJson(this.identifier, this.reader);
+        }
+        verifyJsonResource();
+        final ConfigurationResource resource = new ConfigurationResource();
+
+        for (final Map.Entry<String, JsonValue> entry : this.jsonObject.entrySet()) {
+            if (entry.getKey().startsWith(ConfigurationResource.CONFIGURATOR_PROPERTY_PREFIX)) {
+                // internal property
+                resource.getProperties().put(entry.getKey(), JsonSupport.convertToObject(entry.getValue()));
+            } else if (entry.getValue().getValueType() != ValueType.OBJECT) {
+                addError("Ignoring property (not a configuration) : ".concat(entry.getKey()));
+                resource.getProperties().put(entry.getKey(), JsonSupport.convertToObject(entry.getValue()));
+            } else {
+                final Hashtable<String, Object> properties = readSingleConfiguration(entry.getKey(),
+                        entry.getValue().asJsonObject());
+                if (properties != null) {
+                    resource.getConfigurations().put(entry.getKey(), properties);
+                }
+            }
+        }
+        return resource;
+    }
+
+    private void addError(final String msg) {
+        if (this.identifier == null) {
+            this.errors.add(msg);
+        } else {
+            this.errors.add(identifier.concat(" : ").concat(msg));
+        }
+    }
+
+    private void throwIOException(final String msg) throws IOException {
+        if (this.identifier == null) {
+            throw new IOException(msg);
+        }
+        throw new IOException(this.identifier.concat(" : ").concat(msg));
+    }
+
+    /**
+     * Verify the JSON according to the rules
+     *
+     * @param root The JSON root object.
+     */
+    private void verifyJsonResource() throws IOException {
+        final Object version = JsonSupport
+                .convertToObject(this.jsonObject.get(ConfiguratorConstants.PROPERTY_RESOURCE_VERSION));
+        if (version != null) {
+            final int v = Converters.standardConverter().convert(version).defaultValue(-1).to(Integer.class);
+            if (v == -1) {
+                throwIOException("Invalid resource version information : ".concat(version.toString()));
+            }
+            // we only support version 1
+            if (v != 1) {
+                throwIOException("Unknown resource version : ".concat(version.toString()));
+            }
+        }
+        if (!verifyAsBundleResource) {
+            // if this is not a bundle resource
+            // then version and symbolic name must be set
+            final Object rsrcVersion = JsonSupport
+                    .convertToObject(this.jsonObject.get(ConfiguratorConstants.PROPERTY_VERSION));
+            if (rsrcVersion == null) {
+                throwIOException("Missing version information");
+            }
+            if (!(rsrcVersion instanceof String)) {
+                throwIOException("Invalid version information : ".concat(rsrcVersion.toString()));
+            }
+            final Object rsrcName = JsonSupport
+                    .convertToObject(this.jsonObject.get(ConfiguratorConstants.PROPERTY_SYMBOLIC_NAME));
+            if (rsrcName == null) {
+                throwIOException("Missing symbolic name information");
+            }
+            if (!(rsrcName instanceof String)) {
+                throwIOException("Invalid symbolic name information : ".concat(rsrcVersion.toString()));
+            }
+        }
+    }
+
+    public static final class KeyInfo {
+
+        public final String propertyKey;
+
+        public final String typeInfo;
+
+        public final boolean isInternal;
+
+        public final boolean isBinary;
+
+        public KeyInfo(final String mapKey) {
+            this.isInternal = mapKey.startsWith(ConfigurationResource.CONFIGURATOR_PROPERTY_PREFIX);
+            String key = mapKey;
+            if (isInternal) {
+                key = key.substring(ConfigurationResource.CONFIGURATOR_PROPERTY_PREFIX.length());
+            }
+            final int pos = key.indexOf(':');
+            String typeInfo = null;
+            if (pos != -1) {
+                typeInfo = key.substring(pos + 1);
+                key = key.substring(0, pos);
+            }
+            this.propertyKey = key;
+            this.typeInfo = typeInfo;
+            this.isBinary = TypeConverter.TYPE_BINARY.equals(typeInfo) || TypeConverter.TYPE_BINARIES.equals(typeInfo);
+        }
+    }
+
+    /**
+     * Read a single configuration
+     *
+     * @param pid The configuration pid
+     * @param propertyMap The configuration map
+     * @return A valid configuration dictionary or {@code null}
+     * @throws IOException If reading fails
+     */
+    private Hashtable<String, Object> readSingleConfiguration(final String pid,
+            final JsonObject propertyMap) throws IOException {
+
+        final Hashtable<String, Object> properties = Configurations.newConfiguration();
+        boolean valid = true;
+        for (final Map.Entry<String, JsonValue> propEntry : propertyMap.entrySet()) {
+            final String mapKey = propEntry.getKey();
+
+            final KeyInfo keyInfo = new KeyInfo(mapKey);
+
+            if ( keyInfo.isBinary ) {
+                if ( !this.verifyAsBundleResource) {
+                    throwIOException("PID ".concat(pid)
+                            .concat(" : Properties of type binary not allowed for non bundle resource : ")
+                            .concat(keyInfo.propertyKey));
+                }
+                if ( this.binaryHandler == null ) {
+                    throwIOException("PID ".concat(pid)
+                        .concat(" : No handler configured for binary property : ")
+                        .concat(keyInfo.propertyKey));
+                }
+                if ( keyInfo.isInternal ) {
+                    throwIOException("PID ".concat(pid)
+                            .concat(" : Binary values are not allowed for configurator properties : ")
+                            .concat(keyInfo.propertyKey));
+                }
+            }
+
+            if ( keyInfo.isInternal ) {
+                final Object value = JsonSupport.convertToObject(propEntry.getValue());
+                if ( propertyHandler == null ) {
+                    properties.put(keyInfo.propertyKey, value);
+                } else {
+                    propertyHandler.handleConfiguratorProperty(pid, keyInfo.propertyKey, value);
+                }
+            } else {
+                // convert value
+                Object convertedVal = TypeConverter.convertObjectToType(propEntry.getValue(), keyInfo.typeInfo);
+
+                if ( convertedVal == TypeConverter.CONVERSION_FAILED ) {
+                    final String msg = "PID ".concat(pid).concat(" : Invalid value/type for configuration : ")
+                            .concat(mapKey).concat(" : ").concat(propEntry.getValue().toString());
+                    if (!this.verifyAsBundleResource) {
+                        throwIOException(msg);
+                    }
+                    addError(msg);
+                    valid = false;
+                    break;
+                }
+
+                // special handling for binary and binary[]
+                if (keyInfo.isBinary) {
+                    // String or String[] ?
+                    if ( convertedVal instanceof String ) {
+                        final String path = (String)convertedVal;
+                        convertedVal = this.binaryHandler.handleBinaryValue(pid, keyInfo.propertyKey, path);
+                    } else {
+                        final String[] paths = (String[])convertedVal;
+                        final String[] array = new String[paths.length];
+                        convertedVal = array;
+                        for(int i=0;i<paths.length;i++) {
+                            array[i] =  this.binaryHandler.handleBinaryValue(pid, keyInfo.propertyKey, paths[i]);
+                            if ( array[i] == null ) {
+                                convertedVal = null;
+                            }
+                        }
+                    }
+                }
+                // if ( convertedVal is null) this configuration is invalid
+                if ( convertedVal == null ) {
+                    valid = false;
+                } else {
+                    properties.put(keyInfo.propertyKey, convertedVal);
+                }
+            }
+        }
+        return valid ? properties : null;
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationWriterImpl.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationWriterImpl.java
new file mode 100644
index 0000000..a643e6a
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/ConfigurationWriterImpl.java
@@ -0,0 +1,111 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonValue;
+import javax.json.stream.JsonGenerator;
+
+import org.apache.felix.cm.json.ConfigurationResource;
+import org.apache.felix.cm.json.ConfigurationWriter;
+
+public class ConfigurationWriterImpl
+        implements ConfigurationWriter, ConfigurationWriter.Builder {
+
+    private boolean closed = false;
+
+    private JsonGenerator generator;
+
+    private void checkClosed() throws IOException {
+        if (this.closed) {
+            throw new IOException("Writer already closed");
+        }
+        closed = true;
+    }
+
+    @Override
+    public ConfigurationWriter build(final Writer writer) {
+        this.generator = Json.createGeneratorFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true))
+                .createGenerator(new FilterWriter(writer) {
+
+                    @Override
+                    public void close() throws IOException {
+                        // do not close provided writer, just flush
+                        super.flush();
+                    }
+
+                });
+        return this;
+    }
+
+    @Override
+    public ConfigurationWriter build(final JsonGenerator generator) {
+        this.generator = generator;
+        return this;
+    }
+
+    @Override
+    public void writeConfiguration(final Dictionary<String, Object> properties) throws IOException {
+        checkClosed();
+        writeConfigurationInternal(properties);
+    }
+
+    private void writeConfigurationInternal(final Dictionary<String, Object> properties) throws IOException {
+        generator.writeStartObject();
+        final Enumeration<String> e = properties.keys();
+        while (e.hasMoreElements()) {
+            final String name = e.nextElement();
+            final Object value = properties.get(name);
+
+            final Map.Entry<String, JsonValue> entry = TypeConverter.convertObjectToTypedJsonValue(value);
+            final String key;
+            if (TypeConverter.NO_TYPE_INFO.equals(entry.getKey())) {
+                key = name;
+            } else {
+                key = name.concat(":").concat(entry.getKey());
+            }
+            generator.write(key, entry.getValue());
+        }
+        generator.writeEnd();
+    }
+
+    @Override
+    public void writeConfigurationResource(final ConfigurationResource resource) throws IOException {
+        checkClosed();
+        generator.writeStartObject();
+        for (final Map.Entry<String, Object> entry : resource.getProperties().entrySet()) {
+            generator.write(entry.getKey(), JsonSupport.convertToJson(entry.getValue()));
+        }
+        for (final Map.Entry<String, Hashtable<String, Object>> entry : resource.getConfigurations().entrySet()) {
+            generator.writeKey(entry.getKey());
+            writeConfigurationInternal(entry.getValue());
+        }
+        generator.writeEnd();
+
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/JsonSupport.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/JsonSupport.java
new file mode 100644
index 0000000..5b72c96
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/JsonSupport.java
@@ -0,0 +1,305 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
+import javax.json.JsonString;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
+
+import org.apache.felix.cm.json.Configurations;
+
+public class JsonSupport {
+
+    /**
+     * Convert a json value into an object
+     *
+     * @param value The json value
+     * @return The object or {@code null} if the provided value is {@code null} or
+     *         {@code ValueType#NULL}
+     */
+    public static Object convertToObject(final JsonValue value) {
+        if (value == null) {
+            return null;
+        }
+        switch (value.getValueType()) {
+        // type NULL -> return null
+        case NULL:
+            return null;
+        // type TRUE or FALSE -> return boolean
+        case FALSE:
+            return false;
+        case TRUE:
+            return true;
+        // type String -> return String
+        case STRING:
+            return ((JsonString) value).getString();
+        // type Number -> return long or double
+        case NUMBER:
+            final JsonNumber num = (JsonNumber) value;
+            if (num.isIntegral()) {
+                return num.longValue();
+            }
+            return num.doubleValue();
+        // type ARRAY -> return list and call this method for each value
+        case ARRAY:
+            final JsonArray array = value.asJsonArray();
+            final ValueType arrayType = detectArrayType(array);
+            final Object[] objArray;
+            if (arrayType == ValueType.FALSE || arrayType == ValueType.TRUE) {
+                objArray = new Boolean[array.size()];
+            } else if (arrayType == ValueType.NUMBER) {
+                final boolean isLong = ((JsonNumber) array.get(0)).isIntegral();
+                objArray = isLong ? new Long[array.size()] : new Double[array.size()];
+            } else if (arrayType == ValueType.STRING) {
+                objArray = new String[array.size()];
+            } else {
+                objArray = null;
+            }
+            if (objArray != null) {
+                for (int i = 0; i < array.size(); i++) {
+                    objArray[i] = convertToObject(array.get(i));
+                }
+                return objArray;
+            }
+            return array.toString();
+
+        // type OBJECT -> return map
+        default:
+            return value.asJsonObject().toString();
+        }
+    }
+
+    /**
+     * Detect the value type of a json array. If all elements in the array have the
+     * same type, this type is returned. Otherwise {@code ValueType#OBJECT} is
+     * returned. For an empty array {@code ValueType#STRING} is returned.
+     *
+     * @param array The array
+     * @return The detected type
+     */
+    private static ValueType detectArrayType(final JsonArray array) {
+        if (array.size() == 0) {
+            return ValueType.STRING;
+        }
+        final ValueType vt = array.get(0).getValueType();
+        final boolean isLong = vt == ValueType.NUMBER && ((JsonNumber) array.get(0)).isIntegral();
+        for (int i = 1; i < array.size(); i++) {
+            final ValueType ct = array.get(i).getValueType();
+            boolean isSame = false;
+            if (ct == vt) {
+                isSame = true;
+                if (vt == ValueType.NUMBER) {
+                    if (isLong != ((JsonNumber) array.get(i)).isIntegral()) {
+                        isSame = false;
+                    }
+                }
+            } else if (vt == ValueType.TRUE && ct == ValueType.FALSE) {
+                isSame = true;
+            } else if (vt == ValueType.FALSE && ct == ValueType.TRUE) {
+                isSame = true;
+            }
+            if (!isSame) {
+                return ValueType.OBJECT;
+            }
+        }
+        return vt;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static JsonValue convertToJson(final Object object) {
+        if (object == null) {
+            return JsonValue.NULL;
+        } else if (object instanceof String) {
+            return Json.createValue((String) object);
+        } else if (object instanceof Long) {
+            return Json.createValue((Long) object);
+        } else if (object instanceof Integer) {
+            return Json.createValue((Integer) object);
+        } else if (object instanceof Short) {
+            return Json.createValue((Short) object);
+        } else if (object instanceof Byte) {
+            return Json.createValue((Byte) object);
+        } else if (object instanceof Float) {
+            return Json.createValue((Float) object);
+        } else if (object instanceof Double) {
+            return Json.createValue((Double) object);
+        } else if (object instanceof Boolean) {
+            final Boolean value = (Boolean) object;
+            return value ? JsonValue.TRUE : JsonValue.FALSE;
+        } else if (object instanceof Map) {
+            boolean valid = true;
+            final JsonObjectBuilder builder = Json.createObjectBuilder();
+            for (final Map.Entry<Object, Object> entry : ((Map<Object, Object>) object).entrySet()) {
+                final JsonValue value = convertToJson(entry.getValue());
+                if (entry.getKey() instanceof String) {
+                    builder.add(entry.getKey().toString(), value);
+                } else {
+                    valid = false;
+                    break;
+                }
+            }
+            if (valid) {
+                return builder.build();
+            }
+        } else if (object instanceof Collection) {
+            final JsonArrayBuilder builder = Json.createArrayBuilder();
+            for (final Object obj : (Collection) object) {
+                builder.add(convertToJson(obj));
+            }
+            return builder.build();
+        } else if (object.getClass().isArray()) {
+            final JsonArrayBuilder builder = Json.createArrayBuilder();
+            for (int i = 0; i < Array.getLength(object); i++) {
+                builder.add(convertToJson(Array.get(object, i)));
+            }
+            return builder.build();
+        }
+        return Json.createValue(object.toString());
+    }
+
+    /**
+     * Parse a JSON content
+     *
+     * @param identifier     The identifier of the JSON contents (optional)
+     *
+     * @param contentsReader The reader for the contents
+     *
+     * @return The parsed JSON object. throws IOException on failure parsing
+     */
+    public static JsonObject parseJson(final String identifier, final Reader contentsReader) throws IOException {
+        try (final JsonReader reader = Json.createReader(Configurations.jsonCommentAwareReader(new FilterReader(contentsReader) {
+
+            @Override
+            public void close() throws IOException {
+                // do not close reader
+            }
+        }))) {
+            final JsonStructure obj = reader.read();
+            if (obj != null && obj.getValueType() == ValueType.OBJECT) {
+                return (JsonObject) obj;
+            }
+            final String msg = "Invalid JSON, no root object";
+            if (identifier != null) {
+                throw new IOException(identifier.concat(" : ").concat(msg));
+            }
+            throw new IOException(msg);
+        }
+    }
+
+    /**
+     * Create a reader which removes comments from the input
+     * @param reader The input reader
+     * @return The output reader
+     * @throws IOException If something fails
+     */
+    public static Reader createCommentRemovingReader(final Reader reader) throws IOException {
+        final String contents;
+        try (final StringWriter writer = new StringWriter() ){
+            final char[] buf = new char[2048];
+            int l;
+            while ( (l = reader.read(buf)) > 0 ) {
+                writer.write(buf, 0, l);
+            }
+            writer.flush();
+            contents = writer.toString();
+        }
+        final StringReader stringReader = new StringReader(removeComments(contents));
+        return new FilterReader(stringReader) {
+
+            boolean closed = false;
+            @Override
+            public void close() throws IOException {
+                if (!closed) {
+                    closed = true;
+                    reader.close();
+                    super.close();
+                }
+            }
+        };
+    }
+
+    /**
+     * Helper method to remove comments from JSON
+     * @param comments The JSON with comments
+     * @return The JSON without comments
+     */
+    private static String removeComments(final String comments) {
+        final StringBuilder sb = new StringBuilder(comments);
+        int index = 0;
+        boolean insideQuote = false;
+        while ( index < sb.length()) {
+            switch ( sb.charAt(index) ) {
+            case '"' : if ( index == 0 || sb.charAt(index - 1) != '\\') {
+                           insideQuote = !insideQuote;
+                       }
+                       index++;
+                       break;
+            case '/' : if ( !insideQuote && index + 1 < sb.length()) {
+                           if ( sb.charAt(index + 1) == '/') {
+                               // line comment
+                               int m = index + 2;
+                               while ( m < sb.length() && sb.charAt(m) != '\n' ) {
+                                   m++;
+                               }
+                               sb.delete(index, m);
+                           } else if ( sb.charAt(index + 1 ) == '*') {
+                               // block comment
+                               int m = index + 2;
+                               int newlines = 0;
+                               while ( m < sb.length() && (sb.charAt(m) != '/' || sb.charAt(m - 1) != '*')) {
+                                   if ( sb.charAt(m) == '\n') {
+                                       newlines++;
+                                   }
+                                   m++;
+                               }
+                               if ( m == sb.length() ) {
+                                   index = m; // invalid - just go to the end
+                               } else {
+                                   sb.delete(index,  m+1);
+                                   for(int x = 0; x<newlines; x++) {
+                                       sb.insert(index, '\n');
+                                   }
+                               }
+                           }
+                       }
+                       index++;
+                       break;
+            default: index++;
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/OrderedDictionary.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/OrderedDictionary.java
new file mode 100644
index 0000000..5c3f8b4
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/OrderedDictionary.java
@@ -0,0 +1,145 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A dictionary implementation with predictable iteration order.
+ *
+ * Actually this class is a simple adapter from the Dictionary interface
+ * to a synchronized LinkedHashMap
+ */
+public class OrderedDictionary extends Hashtable<String, Object> implements Serializable {
+
+    private static final long serialVersionUID = -525111601546803041L;
+
+    private static class EnumarationImpl<E> implements Enumeration<E> {
+        private final Iterator<E> iterator;
+
+        public EnumarationImpl(Iterator<E> iterator) {
+            this.iterator = iterator;
+        }
+
+        @Override
+        public boolean hasMoreElements() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public E nextElement() {
+            return iterator.next();
+        }
+    }
+
+    private Map<String, Object> map = Collections.synchronizedMap(new LinkedHashMap<String, Object>());
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return map.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return map.containsValue(value);
+    }
+
+    @Override
+    public Enumeration<String> keys() {
+        return new EnumarationImpl<>(map.keySet().iterator());
+    }
+
+    @Override
+    public Enumeration<Object> elements() {
+        return new EnumarationImpl<>(map.values().iterator());
+    }
+
+    @Override
+    public Object get(Object key) {
+        return map.get(key);
+    }
+
+    @Override
+    public Object put(String key, Object value) {
+        // Make sure the value is not null
+        if (value == null) {
+            throw new NullPointerException();
+        }
+
+        return map.put(key, value);
+    }
+
+    @Override
+    public Object remove(Object key) {
+        return map.remove(key);
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ? extends Object> m) {
+        map.putAll(m);
+    }
+
+    @Override
+    public void clear() {
+        map.clear();
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return map.keySet();
+    }
+
+    @Override
+    public Collection<Object> values() {
+        return map.values();
+    }
+
+    @Override
+    public Set<java.util.Map.Entry<String, Object>> entrySet() {
+        return map.entrySet();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return map.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return map.hashCode();
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/impl/TypeConverter.java b/cm.json/src/main/java/org/apache/felix/cm/json/impl/TypeConverter.java
new file mode 100644
index 0000000..1769ae9
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/impl/TypeConverter.java
@@ -0,0 +1,270 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonValue;
+
+import org.osgi.util.converter.Converters;
+import org.osgi.util.converter.TypeReference;
+
+public class TypeConverter {
+
+    public static final String TYPE_BINARY = "binary";
+
+    public static final String TYPE_BINARIES = "binary[]";
+
+    private static final String TYPE_COLLECTION = "Collection";
+
+    public static final String NO_TYPE_INFO = "";
+
+    public static final Object CONVERSION_FAILED = new Object();
+
+    private static final Map<String, Class<?>> TYPE_MAP = new LinkedHashMap<>();
+    private static final Map<String, TypeReference<?>> TYPE_COLLECTION_MAP = new LinkedHashMap<>();
+    static {
+        // scalar types and primitive types
+        TYPE_MAP.put("String", String.class);
+        TYPE_MAP.put("int", Integer.class);
+        TYPE_MAP.put("Integer", Integer.class);
+        TYPE_MAP.put("long", Long.class);
+        TYPE_MAP.put("Long", Long.class);
+        TYPE_MAP.put("float", Float.class);
+        TYPE_MAP.put("Float", Float.class);
+        TYPE_MAP.put("double", Double.class);
+        TYPE_MAP.put("Double", Double.class);
+        TYPE_MAP.put("byte", Byte.class);
+        TYPE_MAP.put("Byte", Byte.class);
+        TYPE_MAP.put("short", Short.class);
+        TYPE_MAP.put("Short", Short.class);
+        TYPE_MAP.put("char", Character.class);
+        TYPE_MAP.put("Character", Character.class);
+        TYPE_MAP.put("boolean", Boolean.class);
+        TYPE_MAP.put("Boolean", Boolean.class);
+         // array of scalar types and primitive types
+        TYPE_MAP.put("String[]", String[].class);
+        TYPE_MAP.put("int[]", int[].class);
+        TYPE_MAP.put("Integer[]", Integer[].class);
+        TYPE_MAP.put("long[]", long[].class);
+        TYPE_MAP.put("Long[]", Long[].class);
+        TYPE_MAP.put("float[]", float[].class);
+        TYPE_MAP.put("Float[]", Float[].class);
+        TYPE_MAP.put("double[]", double[].class);
+        TYPE_MAP.put("Double[]", Double[].class);
+        TYPE_MAP.put("byte[]", byte[].class);
+        TYPE_MAP.put("Byte[]", Byte[].class);
+        TYPE_MAP.put("short[]", short[].class);
+        TYPE_MAP.put("Short[]", Short[].class);
+        TYPE_MAP.put("boolean[]", boolean[].class);
+        TYPE_MAP.put("Boolean[]", Boolean[].class);
+        TYPE_MAP.put("char[]", char[].class);
+        TYPE_MAP.put("Character[]", Character[].class);
+
+        // binaries
+        TYPE_MAP.put(TYPE_BINARY, String.class);
+        TYPE_MAP.put(TYPE_BINARIES, String[].class);
+
+        // Collections of scalar types
+        TYPE_COLLECTION_MAP.put("Collection<String>", new TypeReference<ArrayList<String>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Integer>", new TypeReference<ArrayList<Integer>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Long>", new TypeReference<ArrayList<Long>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Float>", new TypeReference<ArrayList<Float>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Double>", new TypeReference<ArrayList<Double>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Byte>", new TypeReference<ArrayList<Byte>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Short>", new TypeReference<ArrayList<Short>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Character>", new TypeReference<ArrayList<Character>>() {
+        });
+        TYPE_COLLECTION_MAP.put("Collection<Boolean>", new TypeReference<ArrayList<Boolean>>() {
+        });
+    }
+
+    /**
+     * Convert a value to the given type
+     *
+     * @param value    The json value
+     * @param typeInfo Optional type info
+     * @return The converted value or {@code CONVERSION_FAILED} if the conversion failed.
+     * @throws IllegalArgumentException If the type is unknown
+     */
+    public static Object convertObjectToType(
+            final JsonValue jsonValue,
+            final String typeInfo) throws IllegalArgumentException {
+        final Object value = JsonSupport.convertToObject(jsonValue);
+        if (typeInfo == null) {
+            return value;
+        }
+        final Class<?> typeClass = TYPE_MAP.get(typeInfo);
+        if ( typeClass != null ) {
+            Object result = Converters.standardConverter().convert(value).defaultValue(null).to(typeClass);
+            if (result == null && value != null) {
+                result = CONVERSION_FAILED;
+            }
+            return result;
+        }
+        final TypeReference<?> typeReference = TYPE_COLLECTION_MAP.get(typeInfo);
+        if (typeReference != null) {
+            if (value == null) {
+                return Collections.EMPTY_LIST;
+            }
+            Object result = Converters.standardConverter().convert(value).defaultValue(null).to(typeReference);
+            if (result == null) {
+                result = CONVERSION_FAILED;
+            }
+            return result;
+        }
+
+        // raw collection
+        if (TYPE_COLLECTION.equals(typeInfo)) {
+            if (value == null) {
+                return Collections.EMPTY_LIST;
+            } else if (value instanceof String || value instanceof Boolean || value instanceof Long
+                    || value instanceof Double) {
+                return Collections.singletonList(value);
+            }
+            final Collection<Object> c = new ArrayList<>();
+            for (int i = 0; i < Array.getLength(value); i++) {
+                c.add(Array.get(value, i));
+            }
+            return Collections.unmodifiableCollection(c);
+        }
+
+        // unknown type
+        return CONVERSION_FAILED;
+    }
+
+    private static String findType(final Class<?> clazz) {
+        for (final Map.Entry<String, Class<?>> entry : TYPE_MAP.entrySet()) {
+            if (clazz.isAssignableFrom(entry.getValue())) {
+                return entry.getKey();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Convert an object to a {@code JsonValue}.
+     *
+     * @param value The object to convert
+     * @return A map entry where the key contains the type info and the value the
+     *         converted JsonValue.
+     */
+    public static Map.Entry<String, JsonValue> convertObjectToTypedJsonValue(final Object value) {
+        // native types
+        if (value == null || value instanceof Long || value instanceof Double || value instanceof String
+                || value instanceof Boolean) {
+            return new Entry(NO_TYPE_INFO, JsonSupport.convertToJson(value));
+        }
+
+        if (value.getClass().isArray()) {
+            // arrays
+            String typeInfo = findType(value.getClass());
+            if (typeInfo != null) {
+                if ("String[]".equals(typeInfo) || "Boolean[]".equals(typeInfo) || "Long[]".equals(typeInfo)
+                        || "Double[]".equals(typeInfo)) {
+                    typeInfo = NO_TYPE_INFO;
+                }
+                final JsonArrayBuilder jab = Json.createArrayBuilder();
+                for (int i = 0; i < Array.getLength(value); i++) {
+                    jab.add(convertObjectToTypedJsonValue(Array.get(value, i)).getValue());
+                }
+
+                return new Entry(typeInfo, jab.build());
+            }
+
+        } else if (Collection.class.isAssignableFrom(value.getClass())) {
+            // collections
+            final Collection<?> collection = (Collection<?>) value;
+            // get first object to get the type
+            String typeInfo = TypeConverter.TYPE_COLLECTION;
+            final Iterator<?> i = collection.iterator();
+            if (i.hasNext()) {
+                final String colType = findType(i.next().getClass());
+                if (colType != null) {
+                    typeInfo = typeInfo.concat("<").concat(colType).concat(">");
+                }
+            }
+            final JsonArrayBuilder jab = Json.createArrayBuilder();
+            for (final Object obj : collection) {
+                jab.add(JsonSupport.convertToJson(obj));
+            }
+            return new Entry(typeInfo, jab.build());
+        }
+
+        // scalar types - start with special cases for numbers
+        if (value instanceof Integer) {
+            return new Entry("Integer", Json.createValue((Integer) value));
+        } else if (value instanceof Float) {
+            return new Entry("Float", Json.createValue((Float) value));
+        } else if (value instanceof Short) {
+            return new Entry("Short", Json.createValue((Short) value));
+        } else if (value instanceof Byte) {
+            return new Entry("Byte", Json.createValue((Byte) value));
+        } else if (value instanceof Character) {
+            return new Entry("Character", Json.createValue(value.toString()));
+        }
+        final String typeInfo = findType(value.getClass());
+        if (typeInfo != null) {
+            return new Entry(typeInfo, JsonSupport.convertToJson(value.toString()));
+        }
+
+        return new Entry(NO_TYPE_INFO, JsonSupport.convertToJson(value));
+    }
+
+    private static final class Entry implements Map.Entry<String, JsonValue> {
+
+        private final JsonValue value;
+        private final String type;
+
+        public Entry(final String type, final JsonValue value) {
+            this.type = type;
+            this.value = value;
+        }
+
+        @Override
+        public String getKey() {
+            return this.type;
+        }
+
+        @Override
+        public JsonValue getValue() {
+            return this.value;
+        }
+
+        @Override
+        public JsonValue setValue(JsonValue value) {
+            return null;
+        }
+    }
+}
diff --git a/cm.json/src/main/java/org/apache/felix/cm/json/package-info.java b/cm.json/src/main/java/org/apache/felix/cm/json/package-info.java
new file mode 100644
index 0000000..4f9f76a
--- /dev/null
+++ b/cm.json/src/main/java/org/apache/felix/cm/json/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.felix.cm.json;
+
+import org.osgi.annotation.versioning.Version;
\ No newline at end of file
diff --git a/cm.json/src/test/java/org/apache/felix/cm/json/impl/ConfigurationReaderImplTest.java b/cm.json/src/test/java/org/apache/felix/cm/json/impl/ConfigurationReaderImplTest.java
new file mode 100644
index 0000000..606db74
--- /dev/null
+++ b/cm.json/src/test/java/org/apache/felix/cm/json/impl/ConfigurationReaderImplTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ConfigurationReaderImplTest {
+
+    @Test
+    public void testReadSingleConfiguration() throws IOException {
+        final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+        final Dictionary<String, Object> config;
+        try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/single.json");
+                final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+            config = cfgReader.build(jsonReader).readConfiguration();
+        }
+        assertEquals(1, config.size());
+        assertEquals("Hello World", config.get("text"));
+    }
+
+    @Test
+    public void testReadConfigurationResource() throws IOException {
+        final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+        final Map<String, Hashtable<String, Object>> configs;
+        try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/resource.json");
+                final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+            configs = cfgReader.build(jsonReader).readConfigurationResource().getConfigurations();
+        }
+        assertEquals(2, configs.size());
+        assertNotNull(configs.get("config.a"));
+        assertNotNull(configs.get("config.b"));
+        assertEquals("Hello World", configs.get("config.a").get("text"));
+        assertEquals(8080, configs.get("config.b").get("port"));
+    }
+
+    @Test
+    public void testReadConfigurationResourceMissingProperties() throws IOException {
+        final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+        try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/bundle.json");
+                final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+            cfgReader.build(jsonReader).readConfigurationResource();
+            fail();
+        } catch (final IOException ioe) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testReadBundleConfigurationResource() throws IOException {
+        final ConfigurationReaderImpl cfgReader = new ConfigurationReaderImpl();
+        cfgReader.verifyAsBundleResource();
+        final Map<String, Hashtable<String, Object>> configs;
+        try (final InputStream jsonStream = this.getClass().getResourceAsStream("/configs/bundle.json");
+                final Reader jsonReader = new InputStreamReader(jsonStream, StandardCharsets.UTF_8)) {
+            configs = cfgReader.build(jsonReader).readConfigurationResource().getConfigurations();
+        }
+        assertEquals(2, configs.size());
+        assertNotNull(configs.get("config.a"));
+        assertNotNull(configs.get("config.b"));
+        assertEquals("Hello World", configs.get("config.a").get("text"));
+        assertEquals(8080, configs.get("config.b").get("port"));
+    }
+}
diff --git a/cm.json/src/test/java/org/apache/felix/cm/json/impl/JsonSupportTest.java b/cm.json/src/test/java/org/apache/felix/cm/json/impl/JsonSupportTest.java
new file mode 100644
index 0000000..71a192d
--- /dev/null
+++ b/cm.json/src/test/java/org/apache/felix/cm/json/impl/JsonSupportTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.felix.cm.json.Configurations;
+import org.junit.Test;
+
+/**
+ * This class tests the {@code JsonSupport} class, however it calls the api
+ * through the public {@code Configurations} api.
+ */
+public class JsonSupportTest {
+
+    @Test
+    public void testConvertNullToObject() {
+        assertNull(Configurations.convertToObject(null));
+        assertNull(Configurations.convertToObject(JsonValue.NULL));
+    }
+
+    @Test
+    public void testConvertBooleanToObject() {
+        assertEquals(Boolean.TRUE, Configurations.convertToObject(JsonValue.TRUE));
+        assertEquals(Boolean.FALSE, Configurations.convertToObject(JsonValue.FALSE));
+    }
+
+    @Test
+    public void testConvertNumberToObject() {
+        assertEquals(7L, Configurations.convertToObject(Json.createValue(7)));
+        assertEquals(7L, Configurations.convertToObject(Json.createValue((long) 7)));
+        assertEquals(3.1d, Configurations.convertToObject(Json.createValue(3.1)));
+    }
+
+    public void testConvertStringToObject() {
+        assertEquals("hello world", Configurations.convertToObject(Json.createValue("hello world")));
+    }
+
+    public void testConvertObjectToObject() {
+        final JsonObjectBuilder builder = Json.createObjectBuilder();
+        builder.add("hello", "world");
+
+        assertEquals("{\"hello\":\"world\"}", Configurations.convertToObject(builder.build()));
+    }
+
+    @Test
+    public void testConvertBooleanArrayToObject() {
+        final JsonArrayBuilder builder = Json.createArrayBuilder();
+        builder.add(true);
+        builder.add(false);
+
+        assertArrayEquals(new Boolean[] { true, false }, (Boolean[]) Configurations.convertToObject(builder.build()));
+    }
+
+    @Test
+    public void testConvertNumberArrayToObject() {
+        final JsonArrayBuilder lBuilder = Json.createArrayBuilder();
+        lBuilder.add(5);
+        lBuilder.add(3);
+
+        final JsonArrayBuilder dBuilder = Json.createArrayBuilder();
+        dBuilder.add(5.7);
+        dBuilder.add(3.7);
+
+        final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+        mBuilder.add(5.7);
+        mBuilder.add(3);
+
+        assertArrayEquals(new Long[] { 5L, 3L }, (Long[]) Configurations.convertToObject(lBuilder.build()));
+        assertArrayEquals(new Double[] { 5.7, 3.7 }, (Double[]) Configurations.convertToObject(dBuilder.build()));
+        assertEquals("[5.7,3]", Configurations.convertToObject(mBuilder.build()));
+    }
+
+    public void testConvertStringArrayToObject() {
+        final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+        sBuilder.add("hello");
+        sBuilder.add("world");
+
+        assertArrayEquals(new String[] { "hello", "world" },
+                (String[]) Configurations.convertToObject(sBuilder.build()));
+
+        final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+        mBuilder.add("hello");
+        mBuilder.add("3");
+
+        assertArrayEquals(new String[] { "hello", "3" }, (String[]) Configurations.convertToObject(mBuilder.build()));
+    }
+
+    public void testConvertObjectArrayToObject() {
+        final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+        final JsonObjectBuilder o1 = Json.createObjectBuilder();
+        o1.add("a", "1");
+        final JsonObjectBuilder o2 = Json.createObjectBuilder();
+        o2.add("b", 2);
+        sBuilder.add(o1);
+        sBuilder.add(o2);
+
+        assertArrayEquals(new String[] { "{\"a\":\"1\"}", "{\"b\":2}" },
+                (String[]) Configurations.convertToObject(sBuilder.build()));
+    }
+
+    private String parse(final String input) throws IOException {
+        try ( final Reader reader = Configurations.jsonCommentAwareReader(new StringReader(input))) {
+            final StringWriter w = new StringWriter();
+            int l = 0;
+            char[] buf = new char[4096];
+            while ( (l = reader.read(buf)) > 0 ) {
+                w.write(buf, 0, l);
+            }
+            w.flush();
+
+            return w.toString();
+        }
+    }
+
+    @Test public void testLineCommentTop() throws IOException {
+        final String input = "// Some comment\n" +
+                             "{\n"
+                             + "  \"a\" : 1,\n"
+                             + "  \"b\" : 2\n"
+                             + "}\n";
+
+        assertEquals("\n" +
+                "{\n"
+                + "  \"a\" : 1,\n"
+                + "  \"b\" : 2\n"
+                + "}\n", parse(input));
+    }
+
+    @Test public void testLineComment() throws IOException {
+        final String input = "{\n"
+                             + "  \"a\" : 1,\n"
+                             + "  // another comment\n"
+                             + "  \"b\" : 2\n"
+                             + "}\n";
+
+        assertEquals("{\n"
+                + "  \"a\" : 1,\n"
+                + "  \n"
+                + "  \"b\" : 2\n"
+                + "}\n", parse(input));
+    }
+
+    @Test public void testSeveralComments() throws IOException {
+        final String input = "// Some comment\n" +
+                             "{\n"
+                             + "  \"a\" : 1,\n"
+                             + "  // another comment\n"
+                             + "  /** And more\n"
+                             + "   * comments\n"
+                             + "   */\n"
+                             + "  \"b\" : 2\n"
+                             + "}\n";
+
+        assertEquals("\n" +
+                "{\n"
+                + "  \"a\" : 1,\n"
+                + "  \n"
+                + "  \n"
+                + "\n"
+                + "\n"
+                + "  \"b\" : 2\n"
+                + "}\n", parse(input));
+    }
+}
diff --git a/cm.json/src/test/java/org/apache/felix/cm/json/impl/TypeConverterTest.java b/cm.json/src/test/java/org/apache/felix/cm/json/impl/TypeConverterTest.java
new file mode 100644
index 0000000..787d21d
--- /dev/null
+++ b/cm.json/src/test/java/org/apache/felix/cm/json/impl/TypeConverterTest.java
@@ -0,0 +1,650 @@
+/*
+ * 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.felix.cm.json.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.junit.Test;
+
+public class TypeConverterTest {
+
+    @Test
+    public void testInvalidTypeInf() {
+        assertEquals(TypeConverter.CONVERSION_FAILED, TypeConverter.convertObjectToType(null, "foo"));
+    }
+
+    @Test
+    public void testConvertNullToObject() {
+        assertNull(TypeConverter.convertObjectToType(null, null));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, null));
+
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "String"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "int"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Integer"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "long"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Long"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "float"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Float"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "double"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Double"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "byte"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Byte"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "short"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Short"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "char"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Character"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "boolean"));
+        assertNull(TypeConverter.convertObjectToType(JsonValue.NULL, "Boolean"));
+
+        assertArrayEquals(new String[0], (String[]) TypeConverter.convertObjectToType(JsonValue.NULL, "String[]"));
+        assertArrayEquals(new int[0], (int[]) TypeConverter.convertObjectToType(JsonValue.NULL, "int[]"));
+        assertArrayEquals(new Integer[0], (Integer[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Integer[]"));
+        assertArrayEquals(new long[0], (long[]) TypeConverter.convertObjectToType(JsonValue.NULL, "long[]"));
+        assertArrayEquals(new Long[0], (Long[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Long[]"));
+        assertArrayEquals(new float[0], (float[]) TypeConverter.convertObjectToType(JsonValue.NULL, "float[]"), 0.1f);
+        assertArrayEquals(new Float[0], (Float[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Float[]"));
+        assertArrayEquals(new double[0], (double[]) TypeConverter.convertObjectToType(JsonValue.NULL, "double[]"), 0.1);
+        assertArrayEquals(new Double[0], (Double[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Double[]"));
+        assertArrayEquals(new byte[0], (byte[]) TypeConverter.convertObjectToType(JsonValue.NULL, "byte[]"));
+        assertArrayEquals(new Byte[0], (Byte[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Byte[]"));
+        assertArrayEquals(new short[0], (short[]) TypeConverter.convertObjectToType(JsonValue.NULL, "short[]"));
+        assertArrayEquals(new Short[0], (Short[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Short[]"));
+        assertArrayEquals(new char[0], (char[]) TypeConverter.convertObjectToType(JsonValue.NULL, "char[]"));
+        assertArrayEquals(new Character[0],
+                (Character[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Character[]"));
+        assertArrayEquals(new boolean[0], (boolean[]) TypeConverter.convertObjectToType(JsonValue.NULL, "boolean[]"));
+        assertArrayEquals(new Boolean[0], (Boolean[]) TypeConverter.convertObjectToType(JsonValue.NULL, "Boolean[]"));
+
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection")).isEmpty());
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<String>")).isEmpty());
+        assertTrue(
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Integer>")).isEmpty());
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Long>")).isEmpty());
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Float>")).isEmpty());
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Double>")).isEmpty());
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Byte>")).isEmpty());
+        assertTrue(((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Short>")).isEmpty());
+        assertTrue(
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Character>")).isEmpty());
+        assertTrue(
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.NULL, "Collection<Boolean>")).isEmpty());
+    }
+
+    @Test
+    public void testConvertBooleanToObjectNoTypeInfo() {
+        assertEquals(Boolean.TRUE, TypeConverter.convertObjectToType(JsonValue.TRUE, null));
+        assertEquals(Boolean.FALSE, TypeConverter.convertObjectToType(JsonValue.FALSE, null));
+    }
+
+    @Test
+    public void testConvertNumberToObjectNoTypeInfo() {
+        assertEquals(7L, TypeConverter.convertObjectToType(Json.createValue(7), null));
+        assertEquals(7L, TypeConverter.convertObjectToType(Json.createValue((long) 7), null));
+        assertEquals(3.1, TypeConverter.convertObjectToType(Json.createValue(3.1), null));
+    }
+
+    public void testConvertStringToObjectNoTypeInfo() {
+        assertEquals("hello world", TypeConverter.convertObjectToType(Json.createValue("hello world"), null));
+    }
+
+    public void testConvertObjectToObjectNoTypeInfo() {
+        final JsonObjectBuilder builder = Json.createObjectBuilder();
+        builder.add("hello", "world");
+
+        assertEquals("{\"hello\":\"world\"}", TypeConverter.convertObjectToType(builder.build(), null));
+    }
+
+    @Test
+    public void testConvertBooleanArrayToObjectNoTypeInfo() {
+        final JsonArrayBuilder builder = Json.createArrayBuilder();
+        builder.add(true);
+        builder.add(false);
+
+        assertArrayEquals(new Boolean[] { true, false },
+                (Boolean[]) TypeConverter.convertObjectToType(builder.build(), null));
+    }
+
+    @Test
+    public void testConvertNumberArrayToObjectNoTypeInfo() {
+        final JsonArrayBuilder lBuilder = Json.createArrayBuilder();
+        lBuilder.add(5);
+        lBuilder.add(3);
+
+        final JsonArrayBuilder dBuilder = Json.createArrayBuilder();
+        dBuilder.add(5.7);
+        dBuilder.add(3.7);
+
+        final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+        mBuilder.add(5.7);
+        mBuilder.add(3);
+
+        assertArrayEquals(new Long[] { 5L, 3L }, (Long[]) TypeConverter.convertObjectToType(lBuilder.build(), null));
+        assertArrayEquals(new Double[] { 5.7, 3.7 },
+                (Double[]) TypeConverter.convertObjectToType(dBuilder.build(), null));
+        assertEquals("[5.7,3]", TypeConverter.convertObjectToType(mBuilder.build(), null));
+    }
+
+    public void testConvertStringArrayToObjectNoTypeInfo() {
+        final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+        sBuilder.add("hello");
+        sBuilder.add("world");
+
+        assertArrayEquals(new String[] { "hello", "world" },
+                (String[]) TypeConverter.convertObjectToType(sBuilder.build(), null));
+
+        final JsonArrayBuilder mBuilder = Json.createArrayBuilder();
+        mBuilder.add("hello");
+        mBuilder.add("3");
+
+        assertArrayEquals(new String[] { "hello", "3" }, (String[]) TypeConverter.convertObjectToType(mBuilder.build(), null));
+    }
+
+    public void testConvertObjectArrayToObjectNoTypeInfo() {
+        final JsonArrayBuilder sBuilder = Json.createArrayBuilder();
+        final JsonObjectBuilder o1 = Json.createObjectBuilder();
+        o1.add("a", "1");
+        final JsonObjectBuilder o2 = Json.createObjectBuilder();
+        o2.add("b", 2);
+        sBuilder.add(o1);
+        sBuilder.add(o2);
+
+        assertArrayEquals(new String[] { "{\"a\":\"1\"}", "{\"b\":2}" },
+                (String[]) TypeConverter.convertObjectToType(sBuilder.build(), null));
+    }
+
+    @Test
+    public void testConvertStringToObjectWithType() {
+        assertEquals("hello", TypeConverter.convertObjectToType(Json.createValue("hello"), "String"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "int"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Integer"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "long"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Long"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "float"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Float"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "double"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Double"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "byte"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Byte"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "short"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Short"));
+        assertEquals('h', TypeConverter.convertObjectToType(Json.createValue("hello"), "char"));
+        assertEquals('h', TypeConverter.convertObjectToType(Json.createValue("hello"), "Character"));
+        assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("hello"), "boolean"));
+        assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("hello"), "Boolean"));
+
+        assertArrayEquals(new String[] { "hello" },
+                (String[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "String[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "int[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Integer[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "long[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Long[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "float[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Float[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "double[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Double[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "byte[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Byte[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "short[]"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Short[]"));
+        assertArrayEquals(new char[] { 'h', 'e', 'l', 'l', 'o' },
+                (char[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "char[]"));
+        assertArrayEquals(new Character[] { 'h', 'e', 'l', 'l', 'o' },
+                (Character[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "Character[]"));
+        assertArrayEquals(new boolean[] { false },
+                (boolean[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "boolean[]"));
+        assertArrayEquals(new Boolean[] { false },
+                (Boolean[]) TypeConverter.convertObjectToType(Json.createValue("hello"), "Boolean[]"));
+
+        assertArrayEquals(Collections.singletonList("hello").toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection")).toArray());
+        assertArrayEquals(Collections.singletonList("hello").toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<String>"))
+                        .toArray());
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Integer>"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Long>"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Float>"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Double>"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Byte>"));
+        assertEquals(TypeConverter.CONVERSION_FAILED,
+                TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Short>"));
+
+        assertArrayEquals(Arrays.asList('h').toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Character>"))
+                        .toArray());
+        assertArrayEquals(Collections.singletonList(Boolean.FALSE).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("hello"), "Collection<Boolean>"))
+                        .toArray());
+    }
+
+    @Test
+    public void testConvertStringWithNumberToObjectWithType() {
+        assertEquals("1", TypeConverter.convertObjectToType(Json.createValue("1"), "String"));
+        assertEquals(1, TypeConverter.convertObjectToType(Json.createValue("1"), "int"));
+        assertEquals(1, TypeConverter.convertObjectToType(Json.createValue("1"), "Integer"));
+        assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue("1"), "long"));
+        assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue("1"), "Long"));
+        assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue("1"), "float"));
+        assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue("1"), "Float"));
+        assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue("1"), "double"));
+        assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue("1"), "Double"));
+        assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "byte"));
+        assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "Byte"));
+        assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "short"));
+        assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue("1"), "Short"));
+        assertEquals('1', TypeConverter.convertObjectToType(Json.createValue("1"), "char"));
+        assertEquals('1', TypeConverter.convertObjectToType(Json.createValue("1"), "Character"));
+        assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("1"), "boolean"));
+        assertEquals(false, TypeConverter.convertObjectToType(Json.createValue("1"), "Boolean"));
+
+        assertArrayEquals(new String[] { "1" },
+                (String[]) TypeConverter.convertObjectToType(Json.createValue("1"), "String[]"));
+        assertArrayEquals(new int[] { 1 }, (int[]) TypeConverter.convertObjectToType(Json.createValue("1"), "int[]"));
+        assertArrayEquals(new Integer[] { 1 },
+                (Integer[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Integer[]"));
+        assertArrayEquals(new long[] { 1 },
+                (long[]) TypeConverter.convertObjectToType(Json.createValue("1"), "long[]"));
+        assertArrayEquals(new Long[] { 1L },
+                (Long[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Long[]"));
+        assertArrayEquals(new float[] { 1f },
+                (float[]) TypeConverter.convertObjectToType(Json.createValue("1"), "float[]"), 0.1f);
+        assertArrayEquals(new Float[] { 1f },
+                (Float[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Float[]"));
+        assertArrayEquals(new double[] { 1 },
+                (double[]) TypeConverter.convertObjectToType(Json.createValue("1"), "double[]"), 0.1);
+        assertArrayEquals(new Double[] { 1.0 },
+                (Double[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Double[]"));
+        assertArrayEquals(new byte[] { 1 },
+                (byte[]) TypeConverter.convertObjectToType(Json.createValue("1"), "byte[]"));
+        assertArrayEquals(new Byte[] { 1 },
+                (Byte[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Byte[]"));
+        assertArrayEquals(new short[] { 1 },
+                (short[]) TypeConverter.convertObjectToType(Json.createValue("1"), "short[]"));
+        assertArrayEquals(new Short[] { 1 },
+                (Short[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Short[]"));
+        assertArrayEquals(new char[] { '1' },
+                (char[]) TypeConverter.convertObjectToType(Json.createValue("1"), "char[]"));
+        assertArrayEquals(new Character[] { '1' },
+                (Character[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Character[]"));
+        assertArrayEquals(new boolean[] { false },
+                (boolean[]) TypeConverter.convertObjectToType(Json.createValue("1"), "boolean[]"));
+        assertArrayEquals(new Boolean[] { false },
+                (Boolean[]) TypeConverter.convertObjectToType(Json.createValue("1"), "Boolean[]"));
+
+        assertArrayEquals(Collections.singletonList("1").toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection")).toArray());
+        assertArrayEquals(Collections.singletonList("1").toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<String>"))
+                        .toArray());
+        assertArrayEquals(new Integer[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Integer>"))
+                        .toArray());
+        assertArrayEquals(new Long[] { 1L },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Long>"))
+                        .toArray());
+        assertArrayEquals(new Float[] { 1f },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Float>"))
+                        .toArray());
+        assertArrayEquals(new Double[] { 1.0 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Double>"))
+                        .toArray());
+        assertArrayEquals(new Byte[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Byte>"))
+                        .toArray());
+        assertArrayEquals(new Short[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Short>"))
+                        .toArray());
+        assertArrayEquals(Arrays.asList('1').toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Character>"))
+                        .toArray());
+        assertArrayEquals(Collections.singletonList(Boolean.FALSE).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue("1"), "Collection<Boolean>"))
+                        .toArray());
+    }
+
+    @Test
+    public void testConvertNumberToObjectWithType() {
+        assertEquals("1", TypeConverter.convertObjectToType(Json.createValue(1), "String"));
+        assertEquals(1, TypeConverter.convertObjectToType(Json.createValue(1), "int"));
+        assertEquals(1, TypeConverter.convertObjectToType(Json.createValue(1), "Integer"));
+        assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue(1), "long"));
+        assertEquals(1L, TypeConverter.convertObjectToType(Json.createValue(1), "Long"));
+        assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue(1), "float"));
+        assertEquals(1.0f, TypeConverter.convertObjectToType(Json.createValue(1), "Float"));
+        assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue(1), "double"));
+        assertEquals(1.0, TypeConverter.convertObjectToType(Json.createValue(1), "Double"));
+        assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue(1), "byte"));
+        assertEquals((byte) 1, TypeConverter.convertObjectToType(Json.createValue(1), "Byte"));
+        assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue(1), "short"));
+        assertEquals((short) 1, TypeConverter.convertObjectToType(Json.createValue(1), "Short"));
+        assertEquals((char)1, TypeConverter.convertObjectToType(Json.createValue(1), "char"));
+        assertEquals(Character.valueOf((char)1), TypeConverter.convertObjectToType(Json.createValue(1), "Character"));
+        assertEquals(true, TypeConverter.convertObjectToType(Json.createValue(1), "boolean"));
+        assertEquals(true, TypeConverter.convertObjectToType(Json.createValue(1), "Boolean"));
+
+        assertArrayEquals(new String[] { "1" },
+                (String[]) TypeConverter.convertObjectToType(Json.createValue(1), "String[]"));
+        assertArrayEquals(new int[] { 1 }, (int[]) TypeConverter.convertObjectToType(Json.createValue(1), "int[]"));
+        assertArrayEquals(new Integer[] { 1 },
+                (Integer[]) TypeConverter.convertObjectToType(Json.createValue(1), "Integer[]"));
+        assertArrayEquals(new long[] { 1 },
+                (long[]) TypeConverter.convertObjectToType(Json.createValue(1), "long[]"));
+        assertArrayEquals(new Long[] { 1L },
+                (Long[]) TypeConverter.convertObjectToType(Json.createValue(1), "Long[]"));
+        assertArrayEquals(new float[] { 1f },
+                (float[]) TypeConverter.convertObjectToType(Json.createValue(1), "float[]"), 0.1f);
+        assertArrayEquals(new Float[] { 1f },
+                (Float[]) TypeConverter.convertObjectToType(Json.createValue(1), "Float[]"));
+        assertArrayEquals(new double[] { 1 },
+                (double[]) TypeConverter.convertObjectToType(Json.createValue(1), "double[]"), 0.1);
+        assertArrayEquals(new Double[] { 1.0 },
+                (Double[]) TypeConverter.convertObjectToType(Json.createValue(1), "Double[]"));
+        assertArrayEquals(new byte[] { 1 },
+                (byte[]) TypeConverter.convertObjectToType(Json.createValue(1), "byte[]"));
+        assertArrayEquals(new Byte[] { 1 },
+                (Byte[]) TypeConverter.convertObjectToType(Json.createValue(1), "Byte[]"));
+        assertArrayEquals(new short[] { 1 },
+                (short[]) TypeConverter.convertObjectToType(Json.createValue(1), "short[]"));
+        assertArrayEquals(new Short[] { 1 },
+                (Short[]) TypeConverter.convertObjectToType(Json.createValue(1), "Short[]"));
+        assertArrayEquals(new char[] { (char)1 },
+                (char[]) TypeConverter.convertObjectToType(Json.createValue(1), "char[]"));
+        assertArrayEquals(new Character[] { Character.valueOf((char)1) },
+                (Character[]) TypeConverter.convertObjectToType(Json.createValue(1), "Character[]"));
+        assertArrayEquals(new boolean[] { true },
+                (boolean[]) TypeConverter.convertObjectToType(Json.createValue(1), "boolean[]"));
+        assertArrayEquals(new Boolean[] { true },
+                (Boolean[]) TypeConverter.convertObjectToType(Json.createValue(1), "Boolean[]"));
+
+        assertArrayEquals(Collections.singletonList(1L).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection")).toArray());
+        assertArrayEquals(Collections.singletonList("1").toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<String>"))
+                        .toArray());
+        assertArrayEquals(new Integer[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Integer>"))
+                        .toArray());
+        assertArrayEquals(new Long[] { 1L },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Long>"))
+                        .toArray());
+        assertArrayEquals(new Float[] { 1f },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Float>"))
+                        .toArray());
+        assertArrayEquals(new Double[] { 1.0 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Double>"))
+                        .toArray());
+        assertArrayEquals(new Byte[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Byte>"))
+                        .toArray());
+        assertArrayEquals(new Short[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Short>"))
+                        .toArray());
+        assertArrayEquals(Arrays.asList(Character.valueOf((char)1)).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Character>"))
+                        .toArray());
+        assertArrayEquals(Collections.singletonList(Boolean.TRUE).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(Json.createValue(1), "Collection<Boolean>"))
+                        .toArray());
+    }
+
+    @Test
+    public void testConvertBooleanToObjectWithType() {
+        assertEquals("true", TypeConverter.convertObjectToType(JsonValue.TRUE, "String"));
+        assertEquals(1, TypeConverter.convertObjectToType(JsonValue.TRUE, "int"));
+        assertEquals(1, TypeConverter.convertObjectToType(JsonValue.TRUE, "Integer"));
+        // uncomment below tests due to bug in Apache Felix Converter - FELIX-6242
+//        assertEquals(1L, TypeConverter.convertObjectToType(JsonValue.TRUE, "long"));
+//        assertEquals(1L, TypeConverter.convertObjectToType(JsonValue.TRUE, "Long"));
+//        assertEquals(1.0f, TypeConverter.convertObjectToType(JsonValue.TRUE, "float"));
+//        assertEquals(1.0f, TypeConverter.convertObjectToType(JsonValue.TRUE, "Float"));
+//        assertEquals(1.0, TypeConverter.convertObjectToType(JsonValue.TRUE, "double"));
+//        assertEquals(1.0, TypeConverter.convertObjectToType(JsonValue.TRUE, "Double"));
+//        assertEquals((byte) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "byte"));
+//        assertEquals((byte) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "Byte"));
+//        assertEquals((short) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "short"));
+//        assertEquals((short) 1, TypeConverter.convertObjectToType(JsonValue.TRUE, "Short"));
+        assertEquals((char)1, TypeConverter.convertObjectToType(JsonValue.TRUE, "char"));
+        assertEquals(Character.valueOf((char)1), TypeConverter.convertObjectToType(JsonValue.TRUE, "Character"));
+        assertEquals(true, TypeConverter.convertObjectToType(JsonValue.TRUE, "boolean"));
+        assertEquals(true, TypeConverter.convertObjectToType(JsonValue.TRUE, "Boolean"));
+
+        assertArrayEquals(new String[] { "true" },
+                (String[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "String[]"));
+        assertArrayEquals(new int[] { 1 }, (int[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "int[]"));
+        assertArrayEquals(new Integer[] { 1 },
+                (Integer[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Integer[]"));
+        assertArrayEquals(new long[] { 1 },
+                (long[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "long[]"));
+//        assertArrayEquals(new Long[] { 1L },
+//                (Long[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Long[]"));
+//        assertArrayEquals(new float[] { 1f },
+//                (float[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "float[]"), 0.1f);
+//        assertArrayEquals(new Float[] { 1f },
+//                (Float[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Float[]"));
+//        assertArrayEquals(new double[] { 1 },
+//                (double[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "double[]"), 0.1);
+//        assertArrayEquals(new Double[] { 1.0 },
+//                (Double[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Double[]"));
+//        assertArrayEquals(new byte[] { 1 },
+//                (byte[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "byte[]"));
+//        assertArrayEquals(new Byte[] { 1 },
+//                (Byte[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Byte[]"));
+//        assertArrayEquals(new short[] { 1 },
+//                (short[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "short[]"));
+//        assertArrayEquals(new Short[] { 1 },
+//                (Short[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Short[]"));
+        assertArrayEquals(new char[] { (char)1 },
+                (char[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "char[]"));
+        assertArrayEquals(new Character[] { Character.valueOf((char)1) },
+                (Character[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Character[]"));
+        assertArrayEquals(new boolean[] { true },
+                (boolean[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "boolean[]"));
+        assertArrayEquals(new Boolean[] { true },
+                (Boolean[]) TypeConverter.convertObjectToType(JsonValue.TRUE, "Boolean[]"));
+
+        assertArrayEquals(Collections.singletonList(Boolean.TRUE).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection")).toArray());
+        assertArrayEquals(Collections.singletonList("true").toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<String>"))
+                        .toArray());
+        assertArrayEquals(new Integer[] { 1 },
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Integer>"))
+                        .toArray());
+//        assertArrayEquals(new Long[] { 1L },
+//                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Long>"))
+//                        .toArray());
+//        assertArrayEquals(new Float[] { 1f },
+//                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Float>"))
+//                        .toArray());
+//        assertArrayEquals(new Double[] { 1.0 },
+//                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Double>"))
+//                        .toArray());
+//        assertArrayEquals(new Byte[] { 1 },
+//                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Byte>"))
+//                        .toArray());
+//        assertArrayEquals(new Short[] { 1 },
+//                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Short>"))
+//                        .toArray());
+        assertArrayEquals(Arrays.asList(Character.valueOf((char)1)).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Character>"))
+                        .toArray());
+        assertArrayEquals(Collections.singletonList(Boolean.TRUE).toArray(),
+                ((Collection<?>) TypeConverter.convertObjectToType(JsonValue.TRUE, "Collection<Boolean>"))
+                        .toArray());
+    }
+
+    private void assertEntry(final String typeInfo, final JsonValue jsonValue,
+            final Map.Entry<String, JsonValue> entry) {
+        assertEquals(typeInfo, entry.getKey());
+        assertNotNull(entry.getValue());
+        assertEquals(jsonValue, entry.getValue());
+    }
+
+    @Test public void testConvertScalarsToJson() throws Exception {
+         // null
+        assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.NULL,
+                TypeConverter.convertObjectToTypedJsonValue(null));
+
+        // String
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createValue("hello"),
+                TypeConverter.convertObjectToTypedJsonValue("hello"));
+
+        // boolean and Boolean
+        assertEntry(TypeConverter.NO_TYPE_INFO,
+                JsonValue.TRUE, TypeConverter.convertObjectToTypedJsonValue(true));
+        assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.FALSE, TypeConverter.convertObjectToTypedJsonValue(false));
+        assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.TRUE,
+                TypeConverter.convertObjectToTypedJsonValue(Boolean.TRUE));
+        assertEntry(TypeConverter.NO_TYPE_INFO, JsonValue.FALSE,
+                TypeConverter.convertObjectToTypedJsonValue(Boolean.FALSE));
+
+        // double and Double
+        assertEntry(TypeConverter.NO_TYPE_INFO,
+                Json.createValue(1.0), TypeConverter.convertObjectToTypedJsonValue(1.0));
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createValue(3.0),
+                TypeConverter.convertObjectToTypedJsonValue(Double.valueOf(3.0)));
+        // long and Long
+        assertEntry(TypeConverter.NO_TYPE_INFO,
+                Json.createValue((long) 5), TypeConverter.convertObjectToTypedJsonValue((long) 5));
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createValue((long) 5),
+                TypeConverter.convertObjectToTypedJsonValue(Long.valueOf(5)));
+
+        // int and Integer
+        assertEntry("Integer", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(5));
+        assertEntry("Integer", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(Integer.valueOf(5)));
+
+        // float and Float
+        assertEntry("Float", Json.createValue(1.0),
+                TypeConverter.convertObjectToTypedJsonValue((float) 1.0));
+        assertEntry("Float", Json.createValue(3.0), TypeConverter.convertObjectToTypedJsonValue(Float.valueOf(3.0f)));
+
+        // byte and Byte
+        assertEntry("Byte", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue((byte) 5));
+        assertEntry("Byte", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(Byte.valueOf((byte) 5)));
+
+        // short and Short
+        assertEntry("Short", Json.createValue(5),
+                TypeConverter.convertObjectToTypedJsonValue((short) 5));
+        assertEntry("Short", Json.createValue(5), TypeConverter.convertObjectToTypedJsonValue(Short.valueOf((short) 5)));
+
+        // char and Character
+        assertEntry("Character", Json.createValue("a"),
+                TypeConverter.convertObjectToTypedJsonValue('a'));
+        assertEntry("Character", Json.createValue("a"),
+                TypeConverter.convertObjectToTypedJsonValue(Character.valueOf('a')));
+    }
+
+    @Test
+    public void testConvertScalarArraysToJson() throws Exception {
+        // String[]
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add("hello").add("you").build(),
+                TypeConverter.convertObjectToTypedJsonValue(new String[] { "hello", "you" }));
+
+        // boolean[] and Boolean[]
+        assertEntry("boolean[]", Json.createArrayBuilder().add(true).add(false).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new boolean[] { true, false }));
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add(true).add(false).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Boolean[] { true, false }));
+
+        // double[] and Double[]
+        assertEntry("double[]", Json.createArrayBuilder().add(2.0).add(1.0).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new double[] { 2.0, 1.0 }));
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add(2.0).add(1.0).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Double[] { 2.0, 1.0 }));
+
+        // long[] and Long[]
+        assertEntry("long[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new long[] { 2, 1 }));
+        assertEntry(TypeConverter.NO_TYPE_INFO, Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Long[] { (long) 2, (long) 1 }));
+
+        // int[] and Integer[]
+        assertEntry("int[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new int[] { 2, 1 }));
+        assertEntry("Integer[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Integer[] { 2, 1 }));
+
+        // float[] and Float[]
+        assertEntry("float[]", Json.createArrayBuilder().add(2.0).add(1.0).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new float[] { (float) 2.0, (float) 1.0 }));
+        assertEntry("Float[]", Json.createArrayBuilder().add(2.0).add(1.0).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Float[] { (float) 2.0, (float) 1.0 }));
+
+        // byte[] and Byte[]
+        assertEntry("byte[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new byte[] { (byte) 2, (byte) 1 }));
+        assertEntry("Byte[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Byte[] { (byte) 2, (byte) 1 }));
+
+        // short[] and Short[]
+        assertEntry("short[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new short[] { (short) 2, (short) 1 }));
+        assertEntry("Short[]", Json.createArrayBuilder().add(2).add(1).build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Short[] { (short) 2, (short) 1 }));
+
+        // char[] and Character[]
+        assertEntry("char[]", Json.createArrayBuilder().add("a").add("b").build(),
+                TypeConverter.convertObjectToTypedJsonValue(new char[] { 'a', 'b' }));
+        assertEntry("Character[]", Json.createArrayBuilder().add("a").add("b").build(),
+                TypeConverter.convertObjectToTypedJsonValue(new Character[] { 'a', 'b' }));
+    }
+
+}
diff --git a/cm.json/src/test/resources/configs/bundle.json b/cm.json/src/test/resources/configs/bundle.json
new file mode 100644
index 0000000..3413edd
--- /dev/null
+++ b/cm.json/src/test/resources/configs/bundle.json
@@ -0,0 +1,8 @@
+{
+    "config.a" : {
+        "text" : "Hello World"
+    },
+    "config.b" : {
+        "port:Integer" : 8080
+    }
+}
\ No newline at end of file
diff --git a/cm.json/src/test/resources/configs/resource.json b/cm.json/src/test/resources/configs/resource.json
new file mode 100644
index 0000000..c2aebd1
--- /dev/null
+++ b/cm.json/src/test/resources/configs/resource.json
@@ -0,0 +1,10 @@
+{
+    ":configurator:version" : "1.0.0",
+    ":configurator:symbolic-name" : "feature",
+    "config.a" : {
+        "text" : "Hello World"
+    },
+    "config.b" : {
+        "port:Integer" : 8080
+    }
+}
\ No newline at end of file
diff --git a/cm.json/src/test/resources/configs/single.json b/cm.json/src/test/resources/configs/single.json
new file mode 100644
index 0000000..094cf5a
--- /dev/null
+++ b/cm.json/src/test/resources/configs/single.json
@@ -0,0 +1,3 @@
+{
+    "text" : "Hello World"
+}
\ No newline at end of file