[JSON-AST] Publishing native artifact on bintray
diff --git a/build.gradle b/build.gradle
index 1de27b2..dd26167 100644
--- a/build.gradle
+++ b/build.gradle
@@ -33,13 +33,18 @@
         maven { url "https://plugins.gradle.org/m2/" }
         maven { url eapChannel }
         maven { url serializationRepo }
+        maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" }
     }
 
     dependencies {
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$compilerVersion"
         classpath "org.jetbrains.kotlinx:kotlinx-gradle-serialization-plugin:$serializationPluginVersion"
+        classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:${property('konan.version')}"
+
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2'
-        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
+
+        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.2-SNAPSHOT'
+
         classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2"
         classpath "me.champeau.gradle:jmh-gradle-plugin:0.4.5"
         classpath "net.ltgt.gradle:gradle-apt-plugin:0.10"
@@ -74,6 +79,8 @@
 apply plugin: 'kotlin'
 
 subprojects {
+    if (project.name == "native") return
+
     apply plugin: 'kotlinx-serialization'
 
     apply plugin: 'maven-publish'
@@ -119,23 +126,5 @@
         }
     }
 
-    bintray {
-        user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
-        key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
-        publications = ['mavenProject']
-        pkg {
-            repo = 'kotlinx'
-            name = 'kotlinx.serialization.runtime'
-            userOrg = 'kotlin'
-            licenses = ['Apache-2.0']
-            vcsUrl = 'https://github.com/Kotlin/kotlinx.serialization'
-            websiteUrl = 'https://github.com/Kotlin/kotlinx.serialization'
-            issueTrackerUrl = 'https://github.com/Kotlin/kotlinx.serialization/issues'
-
-            githubRepo = 'Kotlin/kotlinx.serialization'
-            version {
-                name = project.version
-            }
-        }
-    }
+    apply plugin: 'com.jfrog.bintray'
 }
diff --git a/gradle.properties b/gradle.properties
index 8bf5c71..cbae617 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,6 +17,7 @@
 kotlin.version=1.2.50
 kotlin.version.snapshot=1.2-SNAPSHOT
 plugin.version=0.5.1
+konan.version=0.7
 
 org.gradle.parallel=true
 org.gradle.caching=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1eb0ae4..7fc1809 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -19,4 +19,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/json/common/build.gradle b/json/common/build.gradle
new file mode 100644
index 0000000..c68d322
--- /dev/null
+++ b/json/common/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'kotlin-platform-common'
+
+sourceSets {
+    main.kotlin.srcDirs = ['src']
+}
+
+dependencies {
+    compile libraries.kotlin_stdlib_common
+
+    testCompile libraries.kotlin_test_annotations_common
+    testCompile libraries.kotlin_test_common
+}
diff --git a/json/common/src/kotlinx/serialization/json/JsonAst.kt b/json/common/src/kotlinx/serialization/json/JsonAst.kt
new file mode 100644
index 0000000..23318d4
--- /dev/null
+++ b/json/common/src/kotlinx/serialization/json/JsonAst.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.serialization.json
+
+/**
+ * Root node for whole JSON DOM
+ */
+sealed class JsonElement
+
+sealed class JsonPrimitive : JsonElement() {
+    protected abstract val content: String
+
+    val asInt: Int get() = content.toInt()
+    val asLong: Long get() = content.toLong()
+
+    val asDouble: Double get() = content.toDouble()
+    val asFloat: Float get() = content.toFloat()
+
+    val asBoolean: Boolean get() = content.toBoolean()
+
+    val str: String get() = content
+}
+
+/**
+ * Represents quoted JSON strings
+ */
+data class JsonString(override val content: String): JsonPrimitive() {
+    private var quotedString: String? = null
+
+    override fun toString(): String = if (quotedString != null) quotedString!! else {
+        quotedString = buildString { printQuoted(content) }
+        quotedString!!
+    }
+}
+
+/**
+ * Represents unquoted JSON primitives (numbers, booleans and null)
+ */
+data class JsonLiteral(override val content: String): JsonPrimitive() {
+    constructor(number: Number): this(number.toString())
+    constructor(boolean: Boolean): this(boolean.toString())
+
+    override fun toString() = content
+}
+
+val JsonNull = JsonLiteral("null")
+
+data class JsonObject(val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content {
+    fun getAsValue(key: String)= content[key] as? JsonPrimitive
+    fun getAsObject(key: String) = content[key] as? JsonObject
+    fun getAsArray(key: String) = content[key] as? JsonArray
+
+    override fun toString(): String {
+        return content.entries.joinToString(
+            prefix = "{",
+            postfix = "}",
+            transform = {(k, v) -> """"$k": $v"""}
+        )
+    }
+}
+
+data class JsonArray(val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
+    fun getAsValue(index: Int) = content.getOrNull(index) as? JsonPrimitive
+    fun getAsObject(index: Int) = content.getOrNull(index) as? JsonObject
+    fun getAsArray(index: Int) = content.getOrNull(index) as? JsonArray
+
+    override fun toString() = content.joinToString(prefix = "[", postfix = "]")
+}
+
+
+class JsonTreeParser(val input: String) {
+    private val p: Parser = Parser(input)
+
+    private fun readObject(): JsonElement {
+        p.requireTc(TC_BEGIN_OBJ) { "Expected start of object" }
+        p.nextToken()
+        val result: MutableMap<String, JsonElement> = hashMapOf()
+        while (true) {
+            if (p.tc == TC_COMMA) p.nextToken()
+            if (!p.canBeginValue) break
+            val key = p.takeStr()
+            p.requireTc(TC_COLON) { "Expected ':'" }
+            p.nextToken()
+            val elem = read()
+            result[key] = elem
+        }
+        p.requireTc(TC_END_OBJ) { "Expected end of object" }
+        p.nextToken()
+        return JsonObject(result)
+    }
+
+    private fun readValue(asLiteral: Boolean = false): JsonElement {
+        val str = p.takeStr()
+        return if (asLiteral) JsonLiteral(str) else JsonString(str)
+    }
+
+    private fun readArray(): JsonElement {
+        p.requireTc(TC_BEGIN_LIST) { "Expected start of array" }
+        p.nextToken()
+        val result: MutableList<JsonElement> = arrayListOf()
+        while (true) {
+            if (p.tc == TC_COMMA) p.nextToken()
+            if (!p.canBeginValue) break
+            val elem = read()
+            result.add(elem)
+        }
+        p.requireTc(TC_END_LIST) { "Expected end of array" }
+        p.nextToken()
+        return JsonArray(result)
+    }
+
+    fun read(): JsonElement {
+        if (!p.canBeginValue) fail(p.curPos, "Can't begin reading value from here")
+        val tc = p.tc
+        return when (tc) {
+            TC_NULL -> JsonNull.also { p.nextToken() }
+            TC_STRING -> readValue(asLiteral = false)
+            TC_OTHER -> readValue(asLiteral = true)
+            TC_BEGIN_OBJ -> readObject()
+            TC_BEGIN_LIST -> readArray()
+            else -> fail(p.curPos, "Can't begin reading element")
+        }
+    }
+
+    fun readFully(): JsonElement {
+        val r = read()
+        p.requireTc(TC_EOF) { "Input wasn't consumed fully" }
+        return r
+    }
+}
diff --git a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonParser.kt b/json/common/src/kotlinx/serialization/json/JsonParser.kt
similarity index 97%
rename from runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonParser.kt
rename to json/common/src/kotlinx/serialization/json/JsonParser.kt
index 1a46f8d..6440fd7 100644
--- a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonParser.kt
+++ b/json/common/src/kotlinx/serialization/json/JsonParser.kt
@@ -1,8 +1,5 @@
 package kotlinx.serialization.json
 
-import kotlinx.serialization.SerializationException
-import kotlinx.serialization.internal.createString
-
 // special strings
 internal const val NULL = "null"
 
@@ -261,11 +258,11 @@
             when (tc) {
                 TC_BEGIN_LIST, TC_BEGIN_OBJ -> tokenStack.add(tc)
                 TC_END_LIST -> {
-                    if (tokenStack.last() != TC_BEGIN_LIST) throw SerializationException("Invalid JSON at $curPos: found ] instead of }")
+                    if (tokenStack.last() != TC_BEGIN_LIST) throw IllegalStateException("Invalid JSON at $curPos: found ] instead of }")
                     tokenStack.removeAt(tokenStack.size - 1)
                 }
                 TC_END_OBJ -> {
-                    if (tokenStack.last() != TC_BEGIN_OBJ) throw SerializationException("Invalid JSON at $curPos: found } instead of ]")
+                    if (tokenStack.last() != TC_BEGIN_OBJ) throw IllegalStateException("Invalid JSON at $curPos: found } instead of ]")
                     tokenStack.removeAt(tokenStack.size - 1)
                 }
             }
diff --git a/json/common/src/kotlinx/serialization/json/StringOps.kt b/json/common/src/kotlinx/serialization/json/StringOps.kt
new file mode 100644
index 0000000..48e97e8
--- /dev/null
+++ b/json/common/src/kotlinx/serialization/json/StringOps.kt
@@ -0,0 +1,54 @@
+package kotlinx.serialization.json
+
+/**
+ * Creates a string by concatenating given chars.
+ * Can be more efficient than `joinToString` on some platforms.
+ *
+ * charArrayOf('a','b','c').createString(2) = "ab"
+ */
+expect fun CharArray.createString(length: Int): String
+
+private fun toHexChar(i: Int) : Char {
+    val d = i and 0xf
+    return if (d < 10) (d + '0'.toInt()).toChar()
+    else (d - 10 + 'a'.toInt()).toChar()
+}
+
+/*
+ * Even though the actual size of this array is 92, it has to be the power of two, otherwise
+ * JVM cannot perform advanced range-check elimination and vectorization in printQuoted
+ */
+private val ESCAPE_CHARS: Array<String?> = arrayOfNulls<String>(128).apply {
+    for (c in 0..0x1f) {
+        val c1 = toHexChar(c shr 12)
+        val c2 = toHexChar(c shr 8)
+        val c3 = toHexChar(c shr 4)
+        val c4 = toHexChar(c)
+        this[c] = "\\u$c1$c2$c3$c4"
+    }
+    this['"'.toInt()] = "\\\""
+    this['\\'.toInt()] = "\\\\"
+    this['\t'.toInt()] = "\\t"
+    this['\b'.toInt()] = "\\b"
+    this['\n'.toInt()] = "\\n"
+    this['\r'.toInt()] = "\\r"
+    this[0x0c] = "\\f"
+}
+
+internal fun StringBuilder.printQuoted(value: String)  {
+    append(STRING)
+    var lastPos = 0
+    val length = value.length
+    for (i in 0 until length) {
+        val c = value[i].toInt()
+        // Do not replace this constant with C2ESC_MAX (which is smaller than ESCAPE_CHARS size),
+        // otherwise JIT won't eliminate range check and won't vectorize this loop
+        if (c >= ESCAPE_CHARS.size) continue // no need to escape
+        val esc = ESCAPE_CHARS[c] ?: continue
+        append(value, lastPos, i) // flush prev
+        append(esc)
+        lastPos = i + 1
+    }
+    append(value, lastPos, length)
+    append(STRING)
+}
diff --git a/json/native/build.gradle b/json/native/build.gradle
new file mode 100644
index 0000000..2170b09
--- /dev/null
+++ b/json/native/build.gradle
@@ -0,0 +1,39 @@
+apply plugin: 'konan'
+
+konanArtifacts {
+    library('jsonparser') {
+        enableMultiplatform true
+        srcDir 'src'
+        srcDir project(':jsonparser').file('src')
+    }
+}
+
+apply plugin: 'maven-publish'
+apply plugin: 'com.jfrog.bintray'
+
+def localMavenRepo = "file://${new File(System.properties['user.home'] as String)}/.m2-kotlin-native"
+def remoteBintrayRepo ="https://dl.bintray.com/sandwwraith/generic/native-libs"
+
+publishing {
+    repositories {
+        maven {
+            url = remoteBintrayRepo
+        }
+    }
+}
+
+bintray {
+    user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
+    key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
+    pkg {
+        userOrg = 'sandwwraith'
+        repo = 'generic'
+        name = "native-libs"
+        licenses = ['Apache-2.0']
+        vcsUrl = 'https://github.com/Kotlin/kotlinx.serialization'
+    }
+}
+
+bintrayUpload.doFirst {
+    publications = project.publishing.publications
+}
diff --git a/json/native/src/kotlinx/serialization/json/StringOps.kt b/json/native/src/kotlinx/serialization/json/StringOps.kt
new file mode 100644
index 0000000..4b93f27
--- /dev/null
+++ b/json/native/src/kotlinx/serialization/json/StringOps.kt
@@ -0,0 +1,7 @@
+package kotlinx.serialization.json
+
+actual fun CharArray.createString(length: Int): String =
+    StringBuilder().also {
+        it.insert(0, this)
+        it.length = length
+    }.toString()
diff --git a/runtime/common/build.gradle b/runtime/common/build.gradle
index f6b57da..dc022ea 100644
--- a/runtime/common/build.gradle
+++ b/runtime/common/build.gradle
@@ -18,6 +18,7 @@
 
 dependencies {
     compile libraries.kotlin_stdlib_common
+    compile project(':jsonparser')
 
     testCompile libraries.kotlin_test_annotations_common
     testCompile libraries.kotlin_test_common
diff --git a/runtime/common/src/main/kotlin/kotlinx/serialization/internal/CommonStrings.kt b/runtime/common/src/main/kotlin/kotlinx/serialization/internal/CommonStrings.kt
deleted file mode 100644
index 21b0e42..0000000
--- a/runtime/common/src/main/kotlin/kotlinx/serialization/internal/CommonStrings.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.serialization.internal
-
-/**
- * Creates a string by concatenating given chars.
- * Can be more efficient than `joinToString` on some platforms.
- *
- * charArrayOf('a','b','c').createString(2) = "ab"
- */
-expect fun CharArray.createString(length: Int): String
diff --git a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JSON.kt b/runtime/common/src/main/kotlin/kotlinx/serialization/json/JSON.kt
index 34677be..a3c80ff 100644
--- a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JSON.kt
+++ b/runtime/common/src/main/kotlin/kotlinx/serialization/json/JSON.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
 package kotlinx.serialization.json
 
 import kotlinx.serialization.*
@@ -330,48 +330,3 @@
     }
     return false
 }
-
-private fun toHexChar(i: Int) : Char {
-    val d = i and 0xf
-    return if (d < 10) (d + '0'.toInt()).toChar()
-    else (d - 10 + 'a'.toInt()).toChar()
-}
-
-/*
- * Even though the actual size of this array is 92, it has to be the power of two, otherwise
- * JVM cannot perform advanced range-check elimination and vectorization in printQuoted
- */
-private val ESCAPE_CHARS: Array<String?> = arrayOfNulls<String>(128).apply {
-    for (c in 0..0x1f) {
-        val c1 = toHexChar(c shr 12)
-        val c2 = toHexChar(c shr 8)
-        val c3 = toHexChar(c shr 4)
-        val c4 = toHexChar(c)
-        this[c] = "\\u$c1$c2$c3$c4"
-    }
-    this['"'.toInt()] = "\\\""
-    this['\\'.toInt()] = "\\\\"
-    this['\t'.toInt()] = "\\t"
-    this['\b'.toInt()] = "\\b"
-    this['\n'.toInt()] = "\\n"
-    this['\r'.toInt()] = "\\r"
-    this[0x0c] = "\\f"
-}
-
-internal fun StringBuilder.printQuoted(value: String)  {
-    append(STRING)
-    var lastPos = 0
-    val length = value.length
-    for (i in 0 until length) {
-        val c = value[i].toInt()
-        // Do not replace this constant with C2ESC_MAX (which is smaller than ESCAPE_CHARS size),
-        // otherwise JIT won't eliminate range check and won't vectorize this loop
-        if (c >= ESCAPE_CHARS.size) continue // no need to escape
-        val esc = ESCAPE_CHARS[c] ?: continue
-        append(value, lastPos, i) // flush prev
-        append(esc)
-        lastPos = i + 1
-    }
-    append(value, lastPos, length)
-    append(STRING)
-}
diff --git a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonAst.kt b/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonAst.kt
deleted file mode 100644
index 4e72010..0000000
--- a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonAst.kt
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright 2018 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.serialization.json
-
-import kotlinx.serialization.*
-import kotlin.reflect.KClass
-
-/**
- * Root node for whole JSON DOM
- */
-sealed class JsonElement
-
-sealed class JsonPrimitive : JsonElement() {
-    protected abstract val content: String
-
-    val asInt: Int get() = content.toInt()
-    val asLong: Long get() = content.toLong()
-
-    val asDouble: Double get() = content.toDouble()
-    val asFloat: Float get() = content.toFloat()
-
-    val asBoolean: Boolean get() = content.toBoolean()
-
-    val str: String get() = content
-}
-
-/**
- * Represents quoted JSON strings
- */
-data class JsonString(override val content: String): JsonPrimitive() {
-    override fun toString() = buildString { printQuoted(content) }
-}
-
-/**
- * Represents unquoted JSON primitives (numbers, booleans and null)
- */
-data class JsonLiteral(override val content: String): JsonPrimitive() {
-    override fun toString() = content
-}
-
-val JsonNull = JsonLiteral("null")
-
-data class JsonObject(val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content {
-    fun getAsValue(key: String)= content[key] as? JsonPrimitive
-    fun getAsObject(key: String) = content[key] as? JsonObject
-    fun getAsArray(key: String) = content[key] as? JsonArray
-
-    override fun toString(): String {
-        return content.entries.joinToString(
-            prefix = "{",
-            postfix = "}",
-            transform = {(k, v) -> """"$k": $v"""}
-        )
-    }
-}
-
-data class JsonArray(val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
-    fun getAsValue(index: Int) = content.getOrNull(index) as? JsonPrimitive
-    fun getAsObject(index: Int) = content.getOrNull(index) as? JsonObject
-    fun getAsArray(index: Int) = content.getOrNull(index) as? JsonArray
-
-    override fun toString() = content.joinToString(prefix = "[", postfix = "]")
-}
-
-
-class JsonTreeParser(val input: String) {
-    private val p: Parser = Parser(input)
-
-    private fun readObject(): JsonElement {
-        p.requireTc(TC_BEGIN_OBJ) { "Expected start of object" }
-        p.nextToken()
-        val result: MutableMap<String, JsonElement> = hashMapOf()
-        while (true) {
-            if (p.tc == TC_COMMA) p.nextToken()
-            if (!p.canBeginValue) break
-            val key = p.takeStr()
-            p.requireTc(TC_COLON) { "Expected ':'" }
-            p.nextToken()
-            val elem = read()
-            result[key] = elem
-        }
-        p.requireTc(TC_END_OBJ) { "Expected end of object" }
-        p.nextToken()
-        return JsonObject(result)
-    }
-
-    private fun readValue(asLiteral: Boolean = false): JsonElement {
-        val str = p.takeStr()
-        return if (asLiteral) JsonLiteral(str) else JsonString(str)
-    }
-
-    private fun readArray(): JsonElement {
-        p.requireTc(TC_BEGIN_LIST) { "Expected start of array" }
-        p.nextToken()
-        val result: MutableList<JsonElement> = arrayListOf()
-        while (true) {
-            if (p.tc == TC_COMMA) p.nextToken()
-            if (!p.canBeginValue) break
-            val elem = read()
-            result.add(elem)
-        }
-        p.requireTc(TC_END_LIST) { "Expected end of array" }
-        p.nextToken()
-        return JsonArray(result)
-    }
-
-    fun read(): JsonElement {
-        if (!p.canBeginValue) fail(p.curPos, "Can't begin reading value from here")
-        val tc = p.tc
-        return when (tc) {
-            TC_NULL -> JsonNull.also { p.nextToken() }
-            TC_STRING -> readValue(asLiteral = false)
-            TC_OTHER -> readValue(asLiteral = true)
-            TC_BEGIN_OBJ -> readObject()
-            TC_BEGIN_LIST -> readArray()
-            else -> fail(p.curPos, "Can't begin reading element")
-        }
-    }
-
-    fun readFully(): JsonElement {
-        val r = read()
-        p.requireTc(TC_EOF) { "Input wasn't consumed fully" }
-        return r
-    }
-}
-
-
-class JsonTreeMapper(val context: SerialContext? = null) {
-    inline fun <reified T : Any> readTree(tree: JsonElement): T = readTree(tree, context.klassSerializer(T::class))
-
-    fun <T> readTree(obj: JsonElement, loader: KSerialLoader<T>): T {
-        if (obj !is JsonObject) throw SerializationException("Can't deserialize primitive on root level")
-        return JsonTreeInput(obj).read(loader)
-    }
-
-    private abstract inner class AbstractJsonTreeInput(open val obj: JsonElement): NamedValueInput() {
-        init {
-            this.context = this@JsonTreeMapper.context
-        }
-
-        override fun composeName(parentName: String, childName: String): String = childName
-
-
-        override fun readBegin(desc: KSerialClassDesc, vararg typeParams: KSerializer<*>): KInput {
-            val curObj = currentTagOrNull?.let { currentElement(it) } ?: obj
-            // todo: more informative exceptions instead of ClassCast
-            return when (desc.kind) {
-                KSerialClassKind.LIST, KSerialClassKind.SET -> JsonTreeListInput(curObj as JsonArray)
-                KSerialClassKind.MAP -> JsonTreeMapInput(curObj as JsonObject)
-                KSerialClassKind.ENTRY -> JsonTreeMapEntryInput(curObj, currentTag)
-                else -> JsonTreeInput(curObj as JsonObject)
-            }
-        }
-
-        protected open fun getValue(tag: String): JsonPrimitive {
-            val currentElement = currentElement(tag)
-            return currentElement as? JsonPrimitive ?: throw SerializationException("Expected from $tag to be primitive but found $currentElement")
-        }
-
-        protected abstract fun currentElement(tag: String): JsonElement
-
-        override fun readTaggedChar(tag: String): Char {
-            val o = getValue(tag)
-            return if (o.str.length == 1) o.str[0] else throw SerializationException("$o can't be represented as Char")
-        }
-
-        override fun <E : Enum<E>> readTaggedEnum(tag: String, enumClass: KClass<E>): E =
-            enumFromName(enumClass, (getValue(tag).str))
-
-        override fun readTaggedNull(tag: String): Nothing? = null
-        override fun readTaggedNotNullMark(tag: String) = currentElement(tag) !== JsonNull
-
-        override fun readTaggedUnit(tag: String) {
-            return
-        }
-
-        override fun readTaggedBoolean(tag: String): Boolean = getValue(tag).asBoolean
-        override fun readTaggedByte(tag: String): Byte = getValue(tag).asInt.toByte()
-        override fun readTaggedShort(tag: String) = getValue(tag).asInt.toShort()
-        override fun readTaggedInt(tag: String) = getValue(tag).asInt
-        override fun readTaggedLong(tag: String) = getValue(tag).asLong
-        override fun readTaggedFloat(tag: String) = getValue(tag).asFloat
-        override fun readTaggedDouble(tag: String) = getValue(tag).asDouble
-        override fun readTaggedString(tag: String) = getValue(tag).str
-
-    }
-
-    private open inner class JsonTreeInput(override val obj: JsonObject) : AbstractJsonTreeInput(obj) {
-
-        private var pos = 0
-
-        override fun readElement(desc: KSerialClassDesc): Int {
-            while (pos < desc.associatedFieldsCount) {
-                val name = desc.getTag(pos++)
-                if (name in obj) return pos - 1
-            }
-            return READ_DONE
-        }
-
-        override fun currentElement(tag: String): JsonElement {
-            return obj.getValue(tag)
-        }
-
-    }
-
-    private inner class JsonTreeMapEntryInput(override val obj: JsonElement, val cTag: String): AbstractJsonTreeInput(obj) {
-
-        override fun currentElement(tag: String): JsonElement = if (tag == "key") {
-            JsonString(cTag)
-        } else {
-            check(tag == "value") {"Found unexpected tag: $tag"}
-            obj
-        }
-
-        override fun readElement(desc: KSerialClassDesc): Int = READ_ALL
-    }
-
-    private inner class JsonTreeMapInput(override val obj: JsonObject): JsonTreeInput(obj) {
-
-        private val keys = obj.keys.toList()
-        private val size: Int = keys.size
-        private var pos = 0
-
-        override fun elementName(desc: KSerialClassDesc, index: Int): String {
-            val i = index - 1
-            return keys[i]
-        }
-
-        override fun readElement(desc: KSerialClassDesc): Int {
-            while (pos < size) {
-                pos++
-                return pos
-            }
-            return READ_DONE
-        }
-    }
-
-    private inner class JsonTreeListInput(override val obj: JsonArray): AbstractJsonTreeInput(obj) {
-
-        override fun currentElement(tag: String): JsonElement {
-            return obj[tag.toInt()]
-        }
-
-        private val size = obj.content.size
-        private var pos = 0 // 0st element is SIZE. use it?
-
-        override fun elementName(desc: KSerialClassDesc, index: Int): String = (index - 1).toString()
-
-        override fun readElement(desc: KSerialClassDesc): Int {
-            while (pos < size) {
-                pos++
-                return pos
-            }
-            return READ_DONE
-        }
-    }
-}
diff --git a/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonTreeMapper.kt b/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonTreeMapper.kt
new file mode 100644
index 0000000..68b32a3
--- /dev/null
+++ b/runtime/common/src/main/kotlin/kotlinx/serialization/json/JsonTreeMapper.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.internal.SIZE_INDEX
+import kotlin.reflect.KClass
+
+class JsonTreeMapper(val context: SerialContext? = null) {
+    inline fun <reified T : Any> readTree(tree: JsonElement): T = readTree(tree, context.klassSerializer(T::class))
+
+    fun <T> readTree(obj: JsonElement, loader: KSerialLoader<T>): T {
+        if (obj !is JsonObject) throw SerializationException("Can't deserialize primitive on root level")
+        return JsonTreeInput(obj).read(loader)
+    }
+
+    fun <T> writeTree(obj: T, saver: KSerialSaver<T>): JsonElement {
+        lateinit var result: JsonElement
+        val output = JsonTreeOutput { result = it }
+        output.write(saver, obj)
+        return result
+    }
+
+    private abstract inner class AbstractJsonTreeOutput(val nodeConsumer: (JsonElement) -> Unit) : NamedValueOutput() {
+        init {
+            this.context = this@JsonTreeMapper.context
+        }
+
+        override fun composeName(parentName: String, childName: String): String = childName
+
+        abstract fun putElement(key: String, element: JsonElement)
+        abstract fun getCurrent(): JsonElement
+
+        override fun writeTaggedNull(tag: String) = putElement(tag, JsonNull)
+
+        override fun writeTaggedInt(tag: String, value: Int) = putElement(tag, JsonLiteral(value))
+        override fun writeTaggedByte(tag: String, value: Byte) = putElement(tag, JsonLiteral(value))
+        override fun writeTaggedShort(tag: String, value: Short) = putElement(tag, JsonLiteral(value))
+        override fun writeTaggedLong(tag: String, value: Long) = putElement(tag, JsonLiteral(value))
+        override fun writeTaggedFloat(tag: String, value: Float) = putElement(tag, JsonLiteral(value))
+        override fun writeTaggedDouble(tag: String, value: Double) = putElement(tag, JsonLiteral(value))
+        override fun writeTaggedBoolean(tag: String, value: Boolean) = putElement(tag, JsonLiteral(value))
+
+        override fun writeTaggedChar(tag: String, value: Char) = putElement(tag, JsonString(value.toString()))
+        override fun writeTaggedString(tag: String, value: String) = putElement(tag, JsonString(value))
+        override fun <E : Enum<E>> writeTaggedEnum(tag: String, enumClass: KClass<E>, value: E) = putElement(tag, JsonString(value.toString()))
+
+        override fun writeTaggedValue(tag: String, value: Any) {
+            putElement(tag, JsonString(value.toString()))
+        }
+
+        override fun writeBegin(desc: KSerialClassDesc, vararg typeParams: KSerializer<*>): KOutput {
+            val consumer =
+                if (currentTagOrNull == null) nodeConsumer
+                else { node -> putElement(currentTag, node) }
+            return when (desc.kind) {
+                KSerialClassKind.LIST, KSerialClassKind.SET -> JsonTreeListOutput(consumer)
+                KSerialClassKind.MAP -> JsonTreeMapOutput(consumer)
+                KSerialClassKind.ENTRY -> JsonTreeEntryOutput(this@AbstractJsonTreeOutput::putElement)
+                else -> JsonTreeOutput(consumer)
+            }
+        }
+
+        override fun writeFinished(desc: KSerialClassDesc) {
+            nodeConsumer(getCurrent())
+        }
+    }
+
+    private open inner class JsonTreeOutput(nodeConsumer: (JsonElement) -> Unit) :
+        AbstractJsonTreeOutput(nodeConsumer) {
+        private val map: MutableMap<String, JsonElement> = hashMapOf()
+
+        override fun putElement(key: String, element: JsonElement) {
+            map[key] = element
+        }
+
+        override fun getCurrent(): JsonElement = JsonObject(map)
+    }
+
+    private inner class JsonTreeMapOutput(nodeConsumer: (JsonElement) -> Unit) : JsonTreeOutput(nodeConsumer) {
+        override fun shouldWriteElement(desc: KSerialClassDesc, tag: String, index: Int): Boolean = index != SIZE_INDEX
+    }
+
+    private inner class JsonTreeListOutput(nodeConsumer: (JsonElement) -> Unit) : AbstractJsonTreeOutput(nodeConsumer) {
+        private val array: ArrayList<JsonElement> = arrayListOf()
+
+        override fun shouldWriteElement(desc: KSerialClassDesc, tag: String, index: Int): Boolean = index != SIZE_INDEX
+
+        override fun putElement(key: String, element: JsonElement) {
+            val idx = key.toInt() - 1
+            array.add(idx, element)
+        }
+
+        override fun getCurrent(): JsonElement = JsonArray(array)
+    }
+
+    private inner class JsonTreeEntryOutput(val entryConsumer: (String, JsonElement) -> Unit) :
+        AbstractJsonTreeOutput({ throw IllegalStateException("Use entryConsumer instead") }) {
+
+        private lateinit var elem: JsonElement
+        private lateinit var tag: String
+
+        override fun putElement(key: String, element: JsonElement) {
+            if (key != "key") {
+                elem = element
+            } else {
+                check(element is JsonString) { "Expected tag to be JsonString" }
+                tag = (element as JsonString).str
+            }
+        }
+
+        override fun getCurrent(): JsonElement = elem
+
+        override fun writeFinished(desc: KSerialClassDesc) {
+            entryConsumer(tag, elem)
+        }
+    }
+
+    private abstract inner class AbstractJsonTreeInput(open val obj: JsonElement): NamedValueInput() {
+        init {
+            this.context = this@JsonTreeMapper.context
+        }
+
+        override fun composeName(parentName: String, childName: String): String = childName
+
+        private inline fun <reified T: JsonElement> checkCast(obj: JsonElement): T {
+            check(obj is T) { "Expected ${T::class} but found ${obj::class}" }
+            return obj as T
+        }
+
+        override fun readBegin(desc: KSerialClassDesc, vararg typeParams: KSerializer<*>): KInput {
+            val curObj = currentTagOrNull?.let { currentElement(it) } ?: obj
+            return when (desc.kind) {
+                KSerialClassKind.LIST, KSerialClassKind.SET -> JsonTreeListInput(checkCast(curObj))
+                KSerialClassKind.MAP -> JsonTreeMapInput(checkCast(curObj))
+                KSerialClassKind.ENTRY -> JsonTreeMapEntryInput(curObj, currentTag)
+                else -> JsonTreeInput(checkCast(curObj))
+            }
+        }
+
+        protected open fun getValue(tag: String): JsonPrimitive {
+            val currentElement = currentElement(tag)
+            return currentElement as? JsonPrimitive ?: throw SerializationException("Expected from $tag to be primitive but found $currentElement")
+        }
+
+        protected abstract fun currentElement(tag: String): JsonElement
+
+        override fun readTaggedChar(tag: String): Char {
+            val o = getValue(tag)
+            return if (o.str.length == 1) o.str[0] else throw SerializationException("$o can't be represented as Char")
+        }
+
+        override fun <E : Enum<E>> readTaggedEnum(tag: String, enumClass: KClass<E>): E =
+            enumFromName(enumClass, (getValue(tag).str))
+
+        override fun readTaggedNull(tag: String): Nothing? = null
+        override fun readTaggedNotNullMark(tag: String) = currentElement(tag) !== JsonNull
+
+        override fun readTaggedUnit(tag: String) {
+            return
+        }
+
+        override fun readTaggedBoolean(tag: String): Boolean = getValue(tag).asBoolean
+        override fun readTaggedByte(tag: String): Byte = getValue(tag).asInt.toByte()
+        override fun readTaggedShort(tag: String) = getValue(tag).asInt.toShort()
+        override fun readTaggedInt(tag: String) = getValue(tag).asInt
+        override fun readTaggedLong(tag: String) = getValue(tag).asLong
+        override fun readTaggedFloat(tag: String) = getValue(tag).asFloat
+        override fun readTaggedDouble(tag: String) = getValue(tag).asDouble
+        override fun readTaggedString(tag: String) = getValue(tag).str
+
+    }
+
+    private open inner class JsonTreeInput(override val obj: JsonObject) : AbstractJsonTreeInput(obj) {
+
+        private var pos = 0
+
+        override fun readElement(desc: KSerialClassDesc): Int {
+            while (pos < desc.associatedFieldsCount) {
+                val name = desc.getTag(pos++)
+                if (name in obj) return pos - 1
+            }
+            return READ_DONE
+        }
+
+        override fun currentElement(tag: String): JsonElement = obj.getValue(tag)
+
+    }
+
+    private inner class JsonTreeMapEntryInput(override val obj: JsonElement, val cTag: String): AbstractJsonTreeInput(obj) {
+
+        override fun currentElement(tag: String): JsonElement = if (tag == "key") {
+            JsonString(cTag)
+        } else {
+            check(tag == "value") { "Found unexpected tag: $tag" }
+            obj
+        }
+    }
+
+    private inner class JsonTreeMapInput(override val obj: JsonObject): JsonTreeInput(obj) {
+
+        private val keys = obj.keys.toList()
+        private val size: Int = keys.size
+        private var pos = 0
+
+        override fun elementName(desc: KSerialClassDesc, index: Int): String {
+            val i = index - 1
+            return keys[i]
+        }
+
+        override fun readElement(desc: KSerialClassDesc): Int {
+            while (pos < size) {
+                pos++
+                return pos
+            }
+            return READ_DONE
+        }
+    }
+
+    private inner class JsonTreeListInput(override val obj: JsonArray): AbstractJsonTreeInput(obj) {
+
+        override fun currentElement(tag: String): JsonElement {
+            return obj[tag.toInt()]
+        }
+
+        private val size = obj.content.size
+        private var pos = 0 // 0st element is SIZE. use it?
+
+        override fun elementName(desc: KSerialClassDesc, index: Int): String = (index - 1).toString()
+
+        override fun readElement(desc: KSerialClassDesc): Int {
+            while (pos < size) {
+                pos++
+                return pos
+            }
+            return READ_DONE
+        }
+    }
+}
diff --git a/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonTreeTest.kt b/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonTreeTest.kt
index 315b4dc..254c5d2 100644
--- a/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonTreeTest.kt
+++ b/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonTreeTest.kt
@@ -43,69 +43,104 @@
     private fun prepare(s: String): JsonElement = JsonTreeParser(s).readFully()
 
     @Test
-    fun dynamicSimpleTest() {
-        val dyn = prepare("{a: 42}")
-        val parsed = JsonTreeMapper().readTree(dyn, Data.serializer())
+    fun readTreeSimple() {
+        val tree = prepare("{a: 42}")
+        val parsed = JsonTreeMapper().readTree(tree, Data.serializer())
         assertEquals(Data(42), parsed)
     }
 
     @Test
-    fun dynamicNestedTest() {
-        val dyn = prepare("""{s:"foo", d:{a:42}}""")
-        val parsed = JsonTreeMapper().readTree<DataWrapper>(dyn)
+    fun readTreeNested() {
+        val tree = prepare("""{s:"foo", d:{a:42}}""")
+        val parsed = JsonTreeMapper().readTree<DataWrapper>(tree)
         val expected = DataWrapper("foo", Data(42))
         assertEquals(expected, parsed)
         assertEquals(3, parsed.s.length)
     }
 
     @Test
-    fun dynamicAllTypesTest() {
-        val dyn = prepare("""{ b: 1, s: 2, i: 3, f: 1.0, d: 42.0, c: "a", B: true, S: "str"}""")
+    fun readTreeAllTypes() {
+        val tree = prepare("""{ b: 1, s: 2, i: 3, f: 1.0, d: 42.0, c: "a", B: true, S: "str"}""")
         val kotlinObj = AllTypes(1, 2, 3, 1.0f, 42.0, 'a', true, "str")
 
-        assertEquals(kotlinObj, JsonTreeMapper().readTree(dyn))
+        assertEquals(kotlinObj, JsonTreeMapper().readTree(tree))
     }
 
     @Test
-    fun dynamicNullableTest() {
-        val dyn1 = prepare("""{s:"foo", d: null}""")
-        val dyn2 = prepare("""{s:"foo"}""")
+    fun readTreeNullable() {
+        val tree1 = prepare("""{s:"foo", d: null}""")
+        val tree2 = prepare("""{s:"foo"}""")
 
-        assertEquals(DataWrapper("foo", null), JsonTreeMapper().readTree<DataWrapper>(dyn1))
-        assertFailsWith(MissingFieldException::class) { JsonTreeMapper().readTree<DataWrapper>(dyn2) }
+        assertEquals(DataWrapper("foo", null), JsonTreeMapper().readTree<DataWrapper>(tree1))
+        assertFailsWith(MissingFieldException::class) { JsonTreeMapper().readTree<DataWrapper>(tree2) }
     }
 
     @Test
-    fun dynamicOptionalTest() {
-        val dyn1 = prepare("""{s:"foo", d: null}""")
-        val dyn2 = prepare("""{s:"foo"}""")
+    fun readTreeOptional() {
+        val tree1 = prepare("""{s:"foo", d: null}""")
+        val tree2 = prepare("""{s:"foo"}""")
 
-        assertEquals(DataWrapperOptional("foo", null), JsonTreeMapper().readTree<DataWrapperOptional>(dyn1))
-        assertEquals(DataWrapperOptional("foo", null), JsonTreeMapper().readTree<DataWrapperOptional>(dyn2))
+        assertEquals(DataWrapperOptional("foo", null), JsonTreeMapper().readTree<DataWrapperOptional>(tree1))
+        assertEquals(DataWrapperOptional("foo", null), JsonTreeMapper().readTree<DataWrapperOptional>(tree2))
     }
 
     @Test
-    fun dynamicListTest() {
-        val dyn1 = prepare("""{l:[1,2]}""")
-        val dyn15 = prepare("""{l:[{a:42},{a:43}]}""")
-        val dyn2 = prepare("""{l:[[],[{a:42}]]}""")
+    fun readTreeList() {
+        val tree1 = prepare("""{l:[1,2]}""")
+        val tree2 = prepare("""{l:[{a:42},{a:43}]}""")
+        val tree3 = prepare("""{l:[[],[{a:42}]]}""")
 
-        assertEquals(IntList(listOf(1, 2)), JsonTreeMapper().readTree<IntList>(dyn1))
-        assertEquals(DataList(listOf(Data(42), Data(43))), JsonTreeMapper().readTree<DataList>(dyn15))
-        assertEquals(ListOfLists(listOf(listOf(), listOf(Data(42)))), JsonTreeMapper().readTree<ListOfLists>(dyn2))
+        assertEquals(IntList(listOf(1, 2)), JsonTreeMapper().readTree<IntList>(tree1))
+        assertEquals(DataList(listOf(Data(42), Data(43))), JsonTreeMapper().readTree<DataList>(tree2))
+        assertEquals(ListOfLists(listOf(listOf(), listOf(Data(42)))), JsonTreeMapper().readTree<ListOfLists>(tree3))
     }
 
     @Test
-    fun dynamicMapTest() {
+    fun readTreeMap() {
         val dyn = prepare("{m : {\"a\": 1, \"b\" : 2}}")
         val m = MapWrapper(mapOf("a" to 1, "b" to 2))
         assertEquals(m, JsonTreeMapper().readTree<MapWrapper>(dyn))
     }
 
     @Test
-    fun dynamicMapComplexTest() {
+    fun readTreeComplexMap() {
         val dyn = prepare("{m : {1: {a: 42}, 2: {a: 43}}}")
         val m = ComplexMapWrapper(mapOf("1" to Data(42), "2" to Data(43)))
         assertEquals(m, JsonTreeMapper().readTree<ComplexMapWrapper>(dyn))
     }
+
+    private inline fun <reified T: Any> writeAndTest(obj: T, printDiagnostics: Boolean = false): Pair<JsonElement, T> {
+        val serial = T::class.serializer()
+        val tree = JsonTreeMapper().writeTree(obj, serial)
+        val str = tree.toString()
+        if (printDiagnostics) println(str)
+        val restored = JsonTreeMapper().readTree(JsonTreeParser(str).readFully(), serial)
+        assertEquals(obj, restored)
+        return tree to restored
+    }
+
+    @Test
+    fun saveSimpleNestedTree() {
+        writeAndTest(DataWrapper("foo", Data(42)))
+    }
+
+    @Test
+    fun saveComplexMapTree() {
+        writeAndTest(ComplexMapWrapper(mapOf("foo" to Data(42), "bar" to Data(43))))
+    }
+
+    @Test
+    fun saveNestedLists() {
+        writeAndTest(ListOfLists(listOf(listOf(), listOf(Data(1), Data(2)))))
+    }
+
+    @Test
+    fun saveOptional() {
+        writeAndTest(DataWrapperOptional("foo", null))
+    }
+
+    @Test
+    fun saveAllTypes() {
+        writeAndTest(AllTypes(1, -2, 100500, 0.0f, 2048.2, 'a', true, "foobar"))
+    }
 }
diff --git a/runtime/js/build.gradle b/runtime/js/build.gradle
index 08e7b34..f0edff4 100644
--- a/runtime/js/build.gradle
+++ b/runtime/js/build.gradle
@@ -27,6 +27,7 @@
 
 dependencies {
     expectedBy project(':common')
+    expectedBy project(':jsonparser')
 
     compile libraries.kotlin_stdlib_js
     testCompile libraries.kotlin_test_js
@@ -71,4 +72,4 @@
     if (project.hasProperty("tests")) args += ["-f", project.property('tests')]
 }
 
-test.dependsOn runQunit
\ No newline at end of file
+test.dependsOn runQunit
diff --git a/runtime/js/src/main/kotlin/kotlinx/serialization/internal/Strings.kt b/runtime/js/src/main/kotlin/kotlinx/serialization/internal/Strings.kt
deleted file mode 100644
index 77eb924..0000000
--- a/runtime/js/src/main/kotlin/kotlinx/serialization/internal/Strings.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.serialization.internal
-
-actual fun CharArray.createString(length: Int): String =
-    joinToString(separator = "", limit = length, truncated = "")
diff --git a/runtime/js/src/main/kotlin/kotlinx/serialization/json/StringOpsImpl.kt b/runtime/js/src/main/kotlin/kotlinx/serialization/json/StringOpsImpl.kt
new file mode 100644
index 0000000..5a4f59e
--- /dev/null
+++ b/runtime/js/src/main/kotlin/kotlinx/serialization/json/StringOpsImpl.kt
@@ -0,0 +1,4 @@
+package kotlinx.serialization.json
+
+actual fun CharArray.createString(length: Int): String =
+    joinToString(separator = "", limit = length, truncated = "")
diff --git a/runtime/js/src/test/kotlin/kotlinx/serialization/StringTest.kt b/runtime/js/src/test/kotlin/kotlinx/serialization/StringTest.kt
index 8b3b25e..f24c854 100644
--- a/runtime/js/src/test/kotlin/kotlinx/serialization/StringTest.kt
+++ b/runtime/js/src/test/kotlin/kotlinx/serialization/StringTest.kt
@@ -17,7 +17,7 @@
 package kotlinx.serialization
 
 import kotlinx.serialization.internal.HexConverter
-import kotlinx.serialization.internal.createString
+import kotlinx.serialization.json.createString
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
diff --git a/runtime/jvm/build.gradle b/runtime/jvm/build.gradle
index 3c25385..6a89ead 100644
--- a/runtime/jvm/build.gradle
+++ b/runtime/jvm/build.gradle
@@ -53,6 +53,7 @@
 
 dependencies {
     expectedBy project(':common')
+    expectedBy project(':jsonparser')
 
     compile libraries.kotlin_stdlib
 
diff --git a/runtime/jvm/src/main/kotlin/kotlinx/serialization/internal/Strings.kt b/runtime/jvm/src/main/kotlin/kotlinx/serialization/internal/Strings.kt
deleted file mode 100644
index 5e62045..0000000
--- a/runtime/jvm/src/main/kotlin/kotlinx/serialization/internal/Strings.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2017 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.serialization.internal
-
-actual fun CharArray.createString(length: Int): String =
-    String(this, 0, length)
diff --git a/runtime/jvm/src/main/kotlin/kotlinx/serialization/json/StringOpsImpl.kt b/runtime/jvm/src/main/kotlin/kotlinx/serialization/json/StringOpsImpl.kt
new file mode 100644
index 0000000..f9f2ff6
--- /dev/null
+++ b/runtime/jvm/src/main/kotlin/kotlinx/serialization/json/StringOpsImpl.kt
@@ -0,0 +1,4 @@
+package kotlinx.serialization.json
+
+actual fun CharArray.createString(length: Int): String =
+    String(this, 0, length)
diff --git a/settings.gradle b/settings.gradle
index 18de8fd..49a11d8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,11 +19,15 @@
 include ':common'
 include ':jvm'
 include ':js'
+include ':native'
+include ':jsonparser'
 include ':configparser'
 include ':benchmark'
 
 project(':common').projectDir = file('./runtime/common')
 project(':jvm').projectDir = file('./runtime/jvm')
 project(':js').projectDir = file('./runtime/js')
+project(':native').projectDir = file('./json/native')
+project(':jsonparser').projectDir = file('./json/common')
 project(':configparser').projectDir = file('./formats/config')
 project(':benchmark').projectDir = file('./benchmark')