diff --git oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/Base64BlobSerializer.java oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/Base64BlobSerializer.java
new file mode 100644
index 0000000..f0ce1e9
--- /dev/null
+++ oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/Base64BlobSerializer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.jackrabbit.oak.json;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.plugins.memory.ArrayBasedBlob;
+import org.apache.jackrabbit.util.Base64;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class Base64BlobSerializer extends BlobSerializer implements BlobDeserializer {
+    private final int maxSize;
+
+    public Base64BlobSerializer() {
+        this((int)FileUtils.ONE_MB);
+    }
+
+    public Base64BlobSerializer(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
+    @Override
+    public String serialize(Blob blob) {
+        checkArgument(blob.length() < maxSize, "Cannot serialize Blob of size [%s] " +
+                "which is more than allowed maxSize of [%s]", blob.length(), maxSize);
+        try {
+            try (InputStream is = blob.getNewStream()) {
+                StringWriter writer = new StringWriter();
+                Base64.encode(is, writer);
+                return writer.toString();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Blob deserialize(String value) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        StringReader reader = new StringReader(value);
+        try {
+            Base64.decode(reader, baos);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return new ArrayBasedBlob(baos.toByteArray());
+    }
+}
diff --git oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/BlobDeserializer.java oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/BlobDeserializer.java
new file mode 100644
index 0000000..1a84544
--- /dev/null
+++ oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/BlobDeserializer.java
@@ -0,0 +1,27 @@
+/*
+ * 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.jackrabbit.oak.json;
+
+import org.apache.jackrabbit.oak.api.Blob;
+
+public interface BlobDeserializer {
+
+    Blob deserialize(String value);
+}
diff --git oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
new file mode 100644
index 0000000..7a0ce2b
--- /dev/null
+++ oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
@@ -0,0 +1,185 @@
+/*
+ * 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.jackrabbit.oak.json;
+
+import java.util.List;
+
+import javax.jcr.PropertyType;
+
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.json.JsopReader;
+import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
+import org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState;
+import org.apache.jackrabbit.oak.plugins.memory.BooleanPropertyState;
+import org.apache.jackrabbit.oak.plugins.memory.DoublePropertyState;
+import org.apache.jackrabbit.oak.plugins.memory.LongPropertyState;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
+import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState;
+import org.apache.jackrabbit.oak.plugins.value.Conversions;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import static java.util.Collections.emptyList;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+
+public class JsonDeserializer {
+    private final BlobDeserializer blobHandler;
+    private final NodeBuilder builder;
+
+    public JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder) {
+        this.blobHandler = blobHandler;
+        this.builder = builder;
+    }
+
+    public JsonDeserializer(BlobDeserializer blobHandler) {
+        this(blobHandler, EMPTY_NODE.builder());
+    }
+
+    public NodeState deserialize(String json){
+        JsopReader reader = new JsopTokenizer(json);
+        reader.read('{');
+        NodeState state = deserialize(reader);
+        reader.read(JsopReader.END);
+        return state;
+    }
+
+    public NodeState deserialize(JsopReader reader){
+        readNode(reader, builder);
+        reader.read('}');
+        return builder.getNodeState();
+    }
+
+    private void readNode(JsopReader reader, NodeBuilder builder) {
+        do {
+            String key = reader.readString();
+            reader.read(':');
+            if (reader.matches('{')) {
+                readNode(reader, builder.child(key));
+                reader.read('}');
+            } else if (reader.matches('[')){
+                builder.setProperty(readArrayProperty(key, reader));
+            } else {
+                builder.setProperty(readProperty(key, reader));
+            }
+        } while (reader.matches(','));
+    }
+
+    /**
+     * Read a {@code PropertyState} from a {@link JsopReader}
+     * @param name  The name of the property state
+     * @param reader  The reader
+     * @return new property state
+     */
+    private PropertyState readProperty(String name, JsopReader reader) {
+        if (reader.matches(JsopReader.NUMBER)) {
+            String number = reader.getToken();
+            try {
+                return new LongPropertyState(name, Long.parseLong(number));
+            } catch (NumberFormatException e) {
+                return new DoublePropertyState(name, Double.parseDouble(number));
+            }
+        } else if (reader.matches(JsopReader.TRUE)) {
+            return BooleanPropertyState.booleanProperty(name, true);
+        } else if (reader.matches(JsopReader.FALSE)) {
+            return BooleanPropertyState.booleanProperty(name, false);
+        } else if (reader.matches(JsopReader.STRING)) {
+            String jsonString = reader.getToken();
+            if (jsonString.startsWith(TypeCodes.EMPTY_ARRAY)) {
+                int type = PropertyType.valueFromName(
+                        jsonString.substring(TypeCodes.EMPTY_ARRAY.length()));
+                return PropertyStates.createProperty(
+                        name, emptyList(), Type.fromTag(type, true));
+            }
+            int split = TypeCodes.split(jsonString);
+            if (split != -1) {
+                int type = TypeCodes.decodeType(split, jsonString);
+                String value = TypeCodes.decodeName(split, jsonString);
+                if (type == PropertyType.BINARY) {
+                    return  BinaryPropertyState.binaryProperty(
+                            name, blobHandler.deserialize(value));
+                } else {
+                    return createProperty(name, value, type);
+                }
+            } else {
+                return StringPropertyState.stringProperty(
+                        name, jsonString);
+            }
+        } else {
+            throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
+        }
+    }
+
+    /**
+     * Read a multi valued {@code PropertyState} from a {@link JsopReader}
+     * @param name  The name of the property state
+     * @param reader  The reader
+     * @return new property state
+     */
+    private PropertyState readArrayProperty(String name, JsopReader reader) {
+        int type = PropertyType.STRING;
+        List<Object> values = Lists.newArrayList();
+        while (!reader.matches(']')) {
+            if (reader.matches(JsopReader.NUMBER)) {
+                String number = reader.getToken();
+                try {
+                    type = PropertyType.LONG;
+                    values.add(Long.parseLong(number));
+                } catch (NumberFormatException e) {
+                    type = PropertyType.DOUBLE;
+                    values.add(Double.parseDouble(number));
+                }
+            } else if (reader.matches(JsopReader.TRUE)) {
+                type = PropertyType.BOOLEAN;
+                values.add(true);
+            } else if (reader.matches(JsopReader.FALSE)) {
+                type = PropertyType.BOOLEAN;
+                values.add(false);
+            } else if (reader.matches(JsopReader.STRING)) {
+                String jsonString = reader.getToken();
+                int split = TypeCodes.split(jsonString);
+                if (split != -1) {
+                    type = TypeCodes.decodeType(split, jsonString);
+                    String value = TypeCodes.decodeName(split, jsonString);
+                    if (type == PropertyType.BINARY) {
+                        values.add(blobHandler.deserialize(value));
+                    } else if (type == PropertyType.DOUBLE) {
+                        values.add(Conversions.convert(value).toDouble());
+                    } else if (type == PropertyType.DECIMAL) {
+                        values.add(Conversions.convert(value).toDecimal());
+                    } else {
+                        values.add(value);
+                    }
+                } else {
+                    type = PropertyType.STRING;
+                    values.add(jsonString);
+                }
+            } else {
+                throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
+            }
+            reader.matches(',');
+        }
+        return createProperty(name, values, Type.fromTag(type, true));
+    }
+
+
+}
diff --git oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java
new file mode 100644
index 0000000..8358323
--- /dev/null
+++ oak-store-spi/src/test/java/org/apache/jackrabbit/oak/json/JsonDeserializerTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.jackrabbit.oak.json;
+
+import java.util.Collections;
+import java.util.Random;
+
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
+import org.apache.jackrabbit.oak.plugins.memory.ArrayBasedBlob;
+import org.apache.jackrabbit.oak.spi.state.EqualsDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Test;
+
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+public class JsonDeserializerTest {
+    private Base64BlobSerializer blobHandler = new Base64BlobSerializer();
+    private Random rnd = new Random();
+
+    @Test
+    public void basicStuff() throws Exception{
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.child("a").setProperty("foo", "bar");
+        builder.child("b").setProperty("foo", 1);
+        assertDeserialization(builder);
+    }
+
+    @Test
+    public void variousPropertyTypes() throws Exception{
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.child("a").setProperty("foo", 10);
+        builder.child("a").setProperty("foo2", "bar");
+        builder.child("a").setProperty("foo3", true);
+        builder.child("a").setProperty("foo4", false);
+        builder.child("a").setProperty("foo5", 1.1);
+        builder.child("a").setProperty("foo6", "nt:base", Type.NAME);
+        builder.child("a").child("b").setProperty("foo", Lists.newArrayList(1L,2L,3L), Type.LONGS);
+        builder.child("a").child("b").setProperty("foo2", Lists.newArrayList("x", "y", "z"), Type.STRINGS);
+        builder.child("a").child("b").setProperty("foo3", Lists.newArrayList(true, false), Type.BOOLEANS);
+        builder.child("a").child("b").setProperty("foo4", Lists.newArrayList(1.1, 1.2), Type.DOUBLES);
+        builder.child("a").child(":c").setProperty("foo", "bar");
+
+        assertDeserialization(builder);
+    }
+    
+    @Test
+    public void emptyProperty() throws Exception{
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.child("a").setProperty("foo", Collections.emptyList(), Type.NAMES);
+        assertDeserialization(builder);
+    }
+
+    @Test
+    public void binaryProperty() throws Exception{
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.child("a").setProperty("foo", createBlob(100));
+        builder.child("b").setProperty("foo", Lists.newArrayList(createBlob(200), createBlob(300)), Type.BINARIES);
+        assertDeserialization(builder);
+    }
+
+    @Test
+    public void singleProperty() throws Exception{
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.child("a").setProperty("foo3", 1.1);
+        //builder.child("a").setProperty("foo3", Lists.newArrayList(true, false), Type.BOOLEANS);
+
+        assertDeserialization(builder);
+
+    }
+
+    private Blob createBlob(int length) {
+        return new ArrayBasedBlob(randomBytes(length));
+    }
+
+    private byte[] randomBytes(int size) {
+        byte[] data = new byte[size];
+        rnd.nextBytes(data);
+        return data;
+    }
+
+    private void assertDeserialization(NodeBuilder builder) {
+        NodeState nodeState = builder.getNodeState();
+        String json = serialize(nodeState);
+        NodeState nodeState2 = deserialize(json);
+        assertTrue(EqualsDiff.equals(nodeState, nodeState2));
+    }
+
+    private NodeState deserialize(String json) {
+        JsonDeserializer deserializer = new JsonDeserializer(blobHandler);
+        return deserializer.deserialize(json);
+    }
+
+    private String serialize(NodeState nodeState){
+        JsopBuilder json = new JsopBuilder();
+        new JsonSerializer(json, blobHandler).serialize(nodeState);
+        return json.toString();
+    }
+
+}
\ No newline at end of file
