You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2014/06/13 15:45:15 UTC

[3/6] initial import

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java
new file mode 100644
index 0000000..653145d
--- /dev/null
+++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonReaderListenerFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.fleece.core;
+
+public interface JsonReaderListenerFactory {
+    JsonReaderListener subObject();
+
+    JsonReaderListener subArray();
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParser.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParser.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParser.java
new file mode 100644
index 0000000..ffcb870
--- /dev/null
+++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonStreamParser.java
@@ -0,0 +1,386 @@
+/*
+ * 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.fleece.core;
+
+import javax.json.JsonException;
+import javax.json.stream.JsonLocation;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParsingException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.nio.charset.Charset;
+
+import static org.apache.fleece.core.Strings.asEscapedChar;
+
+public class JsonStreamParser implements JsonChars, EscapedStringAwareJsonParser {
+    private static final BufferCache<char[]> BUFFER_CACHE = new BufferCache<char[]>(Integer.getInteger("org.apache.fleece.default-char-buffer", 8192) /*BufferedReader.defaultCharBufferSize*/) {
+        @Override
+        protected char[] newValue(final int defaultSize) {
+            return new char[defaultSize];
+        }
+    };
+
+    private final Reader reader;
+    private final int maxStringSize;
+
+    // lexer state
+    private final char[] loadedChars;
+    private int availableLength = -1; // to trigger loading at first read() call
+    private int currentBufferIdx;
+
+    // current state
+    private Event event = null;
+    private Event lastEvent = null;
+    private String currentValue = null;
+    private String escapedValue = null;
+    // location
+    private int line = 1;
+    private int column = 1;
+    private int offset = 0;
+
+    private final ValueBuilder valueBuilder = new ValueBuilder();
+
+    public JsonStreamParser(final Reader reader, final int maxStringLength) {
+        this.reader = reader;
+        this.loadedChars = BUFFER_CACHE.getCache();
+        this.maxStringSize = maxStringLength < 0 ? loadedChars.length : maxStringLength;
+    }
+
+    public JsonStreamParser(final InputStream stream, final int maxStringLength) {
+        this(new InputStreamReader(stream), maxStringLength);
+    }
+
+    public JsonStreamParser(final InputStream in, final Charset charset, final int maxStringLength) {
+        this(new InputStreamReader(in, charset), maxStringLength);
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (event != null) {
+            return loadedChars[currentBufferIdx] != EOF;
+        }
+
+        try {
+            do {
+                readUntilEvent();
+                if (loadedChars[currentBufferIdx] == QUOTE) {
+                    valueBuilder.reset(0); // actually offset = 1 but reset increments idx
+                    boolean escape = false;
+
+                    while (nextChar() != EOF && loadedChars[currentBufferIdx] != QUOTE && currentBufferIdx < valueBuilder.maxEnd) {
+                        if (loadedChars[currentBufferIdx] == ESCAPE_CHAR) {
+                            read();
+                            escape = true;
+                        }
+                        valueBuilder.next();
+                    }
+                    currentValue = valueBuilder.readValue();
+
+                    if (escape) { // this induces an overhead but that's not that often normally
+                        final StringBuilder builder = new StringBuilder(currentValue.length());
+                        boolean escaped = false;
+                        for (final char current : currentValue.toCharArray()) {
+                            if (current == ESCAPE_CHAR) {
+                                escaped = true;
+                                continue;
+                            }
+                            if (!escape) {
+                                builder.append(current);
+                            } else {
+                                builder.append(asEscapedChar(current));
+                            }
+                        }
+                        escapedValue = currentValue;
+                        currentValue = builder.toString();
+                    } else {
+                        escapedValue = null;
+                    }
+
+                    readUntilEvent(); // we need to check if next char is a ':' to know it is a key
+                    if (loadedChars[currentBufferIdx] == KEY_SEPARATOR) {
+                        event = Event.KEY_NAME;
+                    } else {
+                        if (loadedChars[currentBufferIdx] != COMMA && loadedChars[currentBufferIdx] != END_OBJECT_CHAR && loadedChars[currentBufferIdx] != END_ARRAY_CHAR) {
+                            throw new JsonParsingException("expecting end of structure or comma but got " + loadedChars[currentBufferIdx], createLocation());
+                        }
+                        currentBufferIdx--; // we are alredy in place so to avoid offset when calling readUntilEvent() going back
+                        event = Event.VALUE_STRING;
+                    }
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == START_OBJECT_CHAR) {
+                    event = Event.START_OBJECT;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == END_OBJECT_CHAR) {
+                    event = Event.END_OBJECT;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == START_ARRAY_CHAR) {
+                    event = Event.START_ARRAY;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == END_ARRAY_CHAR) {
+                    event = Event.END_ARRAY;
+                    return true;
+                } else if (isNumber()) {
+                    valueBuilder.reset(-1); // reset will increment to check overflow
+                    while (nextChar() != EOF && isNumber() && currentBufferIdx < valueBuilder.maxEnd) {
+                        valueBuilder.next();
+                    }
+                    currentValue = valueBuilder.readValue();
+                    currentBufferIdx--; // we are alredy in place so to avoid offset when calling readUntilEvent() going back
+                    event = Event.VALUE_NUMBER;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == TRUE_T) {
+                    if (read() != TRUE_R || read() != TRUE_U || read() != TRUE_E) {
+                        throw new JsonParsingException("true expected", createLocation());
+                    }
+                    event = Event.VALUE_TRUE;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == FALSE_F) {
+                    if (read() != FALSE_A || read() != FALSE_L || read() != FALSE_S || read() != FALSE_E) {
+                        throw new JsonParsingException("false expected", createLocation());
+                    }
+                    event = Event.VALUE_FALSE;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == NULL_N) {
+                    if (read() != NULL_U || read() != NULL_L || read() != NULL_L) {
+                        throw new JsonParsingException("null expected", createLocation());
+                    }
+                    event = Event.VALUE_NULL;
+                    return true;
+                } else if (loadedChars[currentBufferIdx] == EOF) {
+                    return false;
+                } else if (loadedChars[currentBufferIdx] == COMMA) {
+                    if (event != null && event != Event.KEY_NAME && event != Event.VALUE_STRING && event != Event.VALUE_NUMBER && event != Event.VALUE_TRUE && event != Event.VALUE_FALSE && event != Event.VALUE_NULL) {
+                        throw new JsonParsingException("unexpected comma", createLocation());
+                    }
+                } else {
+                    throw new JsonParsingException("unexpected character: '" + loadedChars[currentBufferIdx] + "'", createLocation());
+                }
+            } while (true);
+        } catch (final IOException e) {
+            throw new JsonParsingException("unknown state", createLocation());
+        }
+    }
+
+    private StringBuilder savePreviousStringBeforeOverflow(int start, StringBuilder previousParts) {
+        final int length = currentBufferIdx - start;
+        previousParts = (previousParts == null ? new StringBuilder(length * 2) : previousParts).append(loadedChars, start, length);
+        return previousParts;
+    }
+
+    private boolean isNumber() {
+        return isNumber(loadedChars[currentBufferIdx]) || loadedChars[currentBufferIdx] == DOT || loadedChars[currentBufferIdx] == MINUS || loadedChars[currentBufferIdx] == PLUS || loadedChars[currentBufferIdx] == EXP_LOWERCASE || loadedChars[currentBufferIdx] == EXP_UPPERCASE;
+    }
+
+    private static boolean isNumber(final char value) {
+        return value >= ZERO && value <= NINE;
+    }
+
+    private void readUntilEvent() throws IOException {
+        read();
+        skipNotEventChars();
+    }
+
+    private void skipNotEventChars() throws IOException {
+        int read = 0;
+        do {
+            final int current = currentBufferIdx;
+            while (currentBufferIdx < availableLength) {
+                if (loadedChars[currentBufferIdx] > SPACE) {
+                    final int diff = currentBufferIdx - current;
+                    offset += diff;
+                    column += diff;
+                    return;
+                } else if (loadedChars[currentBufferIdx] == EOL) {
+                    line++;
+                    column = 0;
+                }
+                currentBufferIdx++;
+            }
+            read();
+            read++;
+        }
+        while (loadedChars[currentBufferIdx] != EOF && read < loadedChars.length); // don't accept more space than buffer size to avoid DoS
+        if (read == loadedChars.length) {
+            throw new JsonParsingException("Too much spaces (>" + loadedChars.length + ")", createLocation());
+        }
+    }
+
+    public JsonLocationImpl createLocation() {
+        return new JsonLocationImpl(line, column, offset);
+    }
+
+    private char read() throws IOException {
+        incr();
+        return nextChar();
+    }
+
+    private char nextChar() throws IOException {
+        if (overflowIfNeeded()) {
+            offset--;
+            column--;
+            return EOF;
+        }
+        return loadedChars[currentBufferIdx];
+    }
+
+    private int incr() {
+        offset++;
+        column++;
+        currentBufferIdx++;
+        return currentBufferIdx;
+    }
+
+    private boolean overflowIfNeeded() throws IOException {
+        if (currentBufferIdx >= availableLength) {
+            availableLength = reader.read(loadedChars, 0, loadedChars.length);
+            currentBufferIdx = 0;
+            if (availableLength <= 0) { // 0 or -1 typically
+                loadedChars[0] = EOF;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Event next() {
+        if (event == null) {
+            hasNext();
+        }
+        lastEvent = event;
+        event = null;
+        return lastEvent;
+    }
+
+    @Override
+    public String getString() {
+        if (lastEvent == Event.KEY_NAME || lastEvent == Event.VALUE_STRING || lastEvent == Event.VALUE_NUMBER) {
+            return currentValue;
+        }
+        throw new IllegalStateException(event + " doesnt support getString()");
+    }
+
+    @Override
+    public boolean isIntegralNumber() {
+        for (int i = 0; i < currentValue.length(); i++) {
+            if (!isNumber(currentValue.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int getInt() {
+        if (lastEvent != Event.VALUE_NUMBER) {
+            throw new IllegalStateException(event + " doesn't support getInt()");
+        }
+        return Integer.parseInt(currentValue);
+    }
+
+    @Override
+    public long getLong() {
+        if (lastEvent != Event.VALUE_NUMBER) {
+            throw new IllegalStateException(event + " doesn't support getLong()");
+        }
+        return Long.parseLong(currentValue);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal() {
+        if (lastEvent != Event.VALUE_NUMBER) {
+            throw new IllegalStateException(event + " doesn't support getBigDecimal()");
+        }
+        return new BigDecimal(currentValue);
+    }
+
+    @Override
+    public JsonLocation getLocation() {
+        return createLocation();
+    }
+
+    @Override
+    public void close() {
+        BUFFER_CACHE.release(loadedChars);
+        try {
+            reader.close();
+        } catch (final IOException e) {
+            throw new JsonException(e.getMessage(), e);
+        }
+    }
+
+    public static JsonLocation location(final JsonParser parser) {
+        if (JsonStreamParser.class.isInstance(parser)) {
+            return JsonStreamParser.class.cast(parser).createLocation();
+        }
+        return new JsonLocationImpl(-1, -1, -1);
+    }
+
+    @Override
+    public String getEscapedString() {
+        return escapedValue;
+    }
+
+    private class ValueBuilder {
+        private int start;
+        private int maxEnd;
+        private StringBuilder previousParts = null;
+
+        public void next() {
+            if (incr() >= availableLength) { // overflow case
+                previousParts = savePreviousStringBeforeOverflow(start, previousParts);
+                start = 0;
+                maxEnd = maxStringSize;
+            }
+        }
+
+        public String readValue() {
+            if (loadedChars[currentBufferIdx] == EOF) {
+                throw new JsonParsingException("Can't read string", createLocation());
+            }
+
+            final int length = currentBufferIdx - start;
+            if (length >= maxStringSize) {
+                throw new JsonParsingException("String too long", createLocation());
+            }
+
+            final String currentValue = new String(loadedChars, start, length);
+            if (previousParts != null && previousParts.length() > 0) {
+                return previousParts.append(currentValue).toString();
+            }
+            return currentValue;
+        }
+
+        public void reset(final int offset) {
+            if (incr() < availableLength) { // direct overflow case
+                start = currentBufferIdx + offset;
+                maxEnd = start + maxStringSize;
+            } else {
+                maxEnd = maxStringSize - (maxEnd - start);
+                start = 0;
+            }
+            if (previousParts != null) {
+                previousParts.setLength(0);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java
new file mode 100644
index 0000000..d0bdfa3
--- /dev/null
+++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonStringImpl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.fleece.core;
+
+import javax.json.JsonString;
+import javax.json.JsonValue;
+
+public class JsonStringImpl implements JsonString {
+    private final String value;
+    private String escape;
+    private Integer hashCode = null;
+
+    public JsonStringImpl(final String value) {
+        this(value, null);
+    }
+
+    public JsonStringImpl(final String value, final String escaped) {
+        this.value = value;
+        this.escape = escaped;
+    }
+
+    @Override
+    public String getString() {
+        return value;
+    }
+
+    @Override
+    public CharSequence getChars() {
+        return value;
+    }
+
+    @Override
+    public ValueType getValueType() {
+        return ValueType.STRING;
+    }
+
+    @Override
+    public String toString() {
+        if (escape == null) {
+            escape = Strings.escape(value);
+        }
+        return escape;
+    }
+
+    @Override
+    public int hashCode() {
+        if (hashCode == null) {
+            hashCode = value.hashCode();
+        }
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return JsonString.class.isInstance(obj) && JsonString.class.cast(obj).getString().equals(value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java
new file mode 100644
index 0000000..4217cf4
--- /dev/null
+++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterFactoryImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.fleece.core;
+
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGeneratorFactory;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public class JsonWriterFactoryImpl implements JsonWriterFactory {
+    private final Map<String, ?> config;
+    private final JsonGeneratorFactory factory;
+
+    public JsonWriterFactoryImpl(final Map<String, ?> config) {
+        this.config = config;
+        this.factory = new JsonGeneratorFactoryImpl(config);
+    }
+
+    @Override
+    public JsonWriter createWriter(final Writer writer) {
+        return new JsonWriterImpl(factory.createGenerator(writer));
+    }
+
+    @Override
+    public JsonWriter createWriter(final OutputStream out) {
+        return createWriter(new OutputStreamWriter(out));
+    }
+
+    @Override
+    public JsonWriter createWriter(final OutputStream out, final Charset charset) {
+        return createWriter(new OutputStreamWriter(out, charset));
+    }
+
+    @Override
+    public Map<String, ?> getConfigInUse() {
+        return config;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java
new file mode 100644
index 0000000..2c3c3a9
--- /dev/null
+++ b/fleece-core/src/main/java/org/apache/fleece/core/JsonWriterImpl.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fleece.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonWriter;
+import javax.json.stream.JsonGenerator;
+import java.io.Flushable;
+import java.io.IOException;
+
+public class JsonWriterImpl implements JsonWriter, Flushable {
+    private final JsonGenerator generator;
+
+    public JsonWriterImpl(final JsonGenerator generator) {
+        this.generator = generator;
+    }
+
+    @Override
+    public void writeArray(final JsonArray array) {
+        generator.write(array);
+    }
+
+    @Override
+    public void writeObject(final JsonObject object) {
+        generator.write(object);
+    }
+
+    @Override
+    public void write(final JsonStructure value) {
+        generator.write(value);
+    }
+
+    @Override
+    public void close() {
+        generator.close();
+    }
+
+    @Override
+    public void flush() throws IOException {
+        generator.flush();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/java/org/apache/fleece/core/Strings.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/java/org/apache/fleece/core/Strings.java b/fleece-core/src/main/java/org/apache/fleece/core/Strings.java
new file mode 100644
index 0000000..bb13093
--- /dev/null
+++ b/fleece-core/src/main/java/org/apache/fleece/core/Strings.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fleece.core;
+
+public class Strings implements JsonChars {
+    private static final BufferCache<StringBuilder> BUFFER_CACHE = new BufferCache<StringBuilder>(Integer.getInteger("org.apache.fleece.default-string-builder", 1024)) {
+        @Override
+        protected StringBuilder newValue(final int defaultSize) {
+            return new StringBuilder(defaultSize);
+        }
+    };
+
+    private static final String UNICODE_PREFIX = "\\u";
+    private static final String UNICODE_PREFIX_HELPER = "000";
+
+    public static char asEscapedChar(final char current) {
+        switch (current) {
+            case 'r':
+                return '\r';
+            case 't':
+                return '\t';
+            case 'b':
+                return '\b';
+            case 'f':
+                return '\f';
+            case 'n':
+                return '\n';
+            case '"':
+                return '\"';
+        }
+        return current;
+    }
+
+    public static String escape(final String value) {
+        final StringBuilder builder = BUFFER_CACHE.getCache();
+        try {
+            for (int i = 0; i < value.length(); i++) {
+                final char c = value.charAt(i);
+                switch (c) {
+                    case QUOTE:
+                    case ESCAPE_CHAR:
+                        builder.append(ESCAPE_CHAR).append(c);
+                        break;
+                    default:
+                        if (c < SPACE) { // we could do a single switch but actually we should rarely enter this if so no need to pay it
+                            switch (c) {
+                                case EOL:
+                                    builder.append("\\n");
+                                    break;
+                                case '\r':
+                                    builder.append("\\r");
+                                    break;
+                                case '\t':
+                                    builder.append("\\t");
+                                    break;
+                                case '\b':
+                                    builder.append("\\b");
+                                    break;
+                                case '\f':
+                                    builder.append("\\f");
+                                    break;
+                                default:
+                                    builder.append(toUnicode(c));
+                            }
+                        } else if ((c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+                            builder.append(toUnicode(c));
+                        } else {
+                            builder.append(c);
+                        }
+                }
+            }
+            final String s = builder.toString();
+            return s;
+        } finally {
+            builder.setLength(0);
+            BUFFER_CACHE.release(builder);
+        }
+    }
+
+    private static String toUnicode(final char c) {
+        final String hex = UNICODE_PREFIX_HELPER + Integer.toHexString(c);
+        return UNICODE_PREFIX + hex.substring(hex.length() - 4);
+    }
+
+    private Strings() {
+        // no-op
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider
----------------------------------------------------------------------
diff --git a/fleece-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider b/fleece-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider
new file mode 100644
index 0000000..0dbd3c3
--- /dev/null
+++ b/fleece-core/src/main/resources/META-INF/services/javax.json.spi.JsonProvider
@@ -0,0 +1 @@
+org.apache.fleece.core.JsonProviderImpl

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayBuilderImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayBuilderImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayBuilderImplTest.java
new file mode 100644
index 0000000..42717c6
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayBuilderImplTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.fleece.core;
+
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonArrayBuilderImplTest {
+    @Test
+    public void array() {
+        final JsonArrayBuilder builder = Json.createArrayBuilder();
+        builder.add("a").add("b");
+        assertEquals("[\"a\",\"b\"]", builder.build().toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java
new file mode 100644
index 0000000..0b89de4
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonArrayImplTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.fleece.core;
+
+import org.apache.fleece.core.JsonArrayImpl;
+import org.apache.fleece.core.JsonStringImpl;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonArrayImplTest {
+    @Test
+    public void arrayToString() {
+        final JsonArrayImpl object = new JsonArrayImpl();
+        object.addInternal(new JsonStringImpl("a"));
+        object.addInternal(new JsonStringImpl("b"));
+        assertEquals("[\"a\",\"b\"]", object.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java
new file mode 100644
index 0000000..6d9c4e5
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonGeneratorImplTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.fleece.core;
+
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.stream.JsonGenerator;
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonGeneratorImplTest {
+    @Test
+    public void emptyArray() {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final JsonGenerator generator = Json.createGenerator(baos);
+        generator.writeStartArray().writeEnd().close();
+        assertEquals("[]", new String(baos.toByteArray()));
+    }
+
+    @Test
+    public void simpleArray() {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Json.createGenerator(baos).writeStartArray().write(1).write(2).writeEnd().close();
+        assertEquals("[1,2]", new String(baos.toByteArray()));
+    }
+
+    @Test
+    public void generate() {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final JsonGenerator generator = Json.createGenerator(baos);
+
+        generator.writeStartObject().write("firstName", "John").write("lastName", "Smith").write("age", 25).writeStartObject("address").write("streetAddress", "21 2nd Street").write("city", "New York").write("state", "NY").write("postalCode", "10021").writeEnd().writeStartArray("phoneNumber").writeStartObject().write("type", "home").write("number", "212 555-1234").writeEnd().writeStartObject().write("type", "fax").write("number", "646 555-4567").writeEnd().writeEnd().writeEnd().close();
+
+        assertEquals("{\"firstName\":\"John\",\"lastName\":\"Smith\",\"age\":25,\"address\":{\"streetAddress\":\"21 2nd Street\",\"city\":\"New York\",\"state\":\"NY\",\"postalCode\":\"10021\"},\"phoneNumber\":[{\"type\":\"home\",\"number\":\"212 555-1234\"},{\"type\":\"fax\",\"number\":\"646 555-4567\"}]}", new String(baos.toByteArray()));
+    }
+
+    @Test
+    public void pretty() {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final JsonGenerator generator = Json.createGeneratorFactory(new HashMap<String, Object>() {{
+            put(JsonGenerator.PRETTY_PRINTING, true);
+        }}).createGenerator(baos);
+
+        generator.writeStartObject().write("firstName", "John").write("lastName", "Smith").write("age", 25).writeStartObject("address").write("streetAddress", "21 2nd Street").write("city", "New York").write("state", "NY").write("postalCode", "10021").writeEnd().writeStartArray("phoneNumber").writeStartObject().write("type", "home").write("number", "212 555-1234").writeEnd().writeStartObject().write("type", "fax").write("number", "646 555-4567").writeEnd().writeEnd().writeEnd().close();
+
+        assertEquals("{\n" +
+                        "  \"firstName\":\"John\",\n" +
+                        "  \"lastName\":\"Smith\",\n" +
+                        "  \"age\":25,\n" +
+                        "  \"address\":{\n" +
+                        "    \"streetAddress\":\"21 2nd Street\",\n" +
+                        "    \"city\":\"New York\",\n" +
+                        "    \"state\":\"NY\",\n" +
+                        "    \"postalCode\":\"10021\"\n" +
+                        "  },\n" +
+                        "  \"phoneNumber\":[\n" +
+                        "    {\n" +
+                        "      \"type\":\"home\",\n" +
+                        "      \"number\":\"212 555-1234\"\n" +
+                        "    },\n" +
+                        "    {\n" +
+                        "      \"type\":\"fax\",\n" +
+                        "      \"number\":\"646 555-4567\"\n" +
+                        "    }\n" +
+                        "  ]\n" +
+                        "}", new String(baos.toByteArray()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectBuilderImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectBuilderImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectBuilderImplTest.java
new file mode 100644
index 0000000..ea14a71
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectBuilderImplTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.fleece.core;
+
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonObjectBuilder;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonObjectBuilderImplTest {
+    @Test
+    public void build() {
+        final JsonObjectBuilder builder = Json.createObjectBuilder();
+        builder.add("a", "b");
+        assertEquals("{\"a\":\"b\"}", builder.build().toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java
new file mode 100644
index 0000000..a7aff07
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonObjectImplTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.fleece.core;
+
+import org.apache.fleece.core.JsonObjectImpl;
+import org.apache.fleece.core.JsonStringImpl;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonObjectImplTest {
+    @Test
+    public void objectToString() {
+        final JsonObjectImpl object = new JsonObjectImpl();
+        object.putInternal("a", new JsonStringImpl("b"));
+        assertEquals("{\"a\":\"b\"}", object.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java
new file mode 100644
index 0000000..6e4727f
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonParserTest.java
@@ -0,0 +1,442 @@
+/*
+ * 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.fleece.core;
+
+import org.apache.fleece.core.JsonArrayImpl;
+import org.apache.fleece.core.JsonNumberImpl;
+import org.apache.fleece.core.JsonObjectImpl;
+import org.apache.fleece.core.JsonParserFactoryImpl;
+import org.apache.fleece.core.JsonReaderImpl;
+import org.apache.fleece.core.JsonStreamParser;
+import org.apache.fleece.core.JsonStringImpl;
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonReader;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParsingException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class JsonParserTest {
+    private void assertSimple(final JsonParser parser) {
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("a", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_STRING, event);
+            assertEquals("b", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("c", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertTrue(parser.isIntegralNumber());
+            assertEquals(4, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("d", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_ARRAY, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertEquals(1, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertEquals(2, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_ARRAY, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_OBJECT, event);
+        }
+        {
+            assertFalse(parser.hasNext());
+        }
+        parser.close();
+    }
+
+    @Test
+    public void array() {
+        final JsonReader loadInMemReader = Json.createReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/array.json"));
+        assertNotNull(loadInMemReader);
+        final JsonArray array = loadInMemReader.readArray();
+        assertNotNull(array);
+
+        final JsonParser parser = Json.createParserFactory(Collections.<String, Object>emptyMap()).createParser(array);
+        assertNotNull(parser);
+
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_ARRAY, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_STRING, event);
+            assertEquals("a", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertEquals(1, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("b", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_STRING, event);
+            assertEquals("c", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertEquals(5, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_ARRAY, event);
+        }
+        {
+            assertFalse(parser.hasNext());
+        }
+    }
+
+    @Test
+    public void simpleInMemory() {
+        final JsonObjectImpl simple = new JsonObjectImpl();
+        simple.putInternal("a", new JsonStringImpl("b"));
+        simple.putInternal("c", new JsonNumberImpl(new BigDecimal(4)));
+        final JsonArrayImpl array = new JsonArrayImpl();
+        array.addInternal(new JsonNumberImpl(new BigDecimal(1)));
+        array.addInternal(new JsonNumberImpl(new BigDecimal(2)));
+        simple.putInternal("d", array);
+
+        final JsonParser parser = Json.createParserFactory(Collections.<String, Object>emptyMap()).createParser(simple);
+        assertNotNull(parser);
+        assertSimple(parser);
+    }
+
+    @Test
+    public void simple() {
+        final JsonParser parser = Json.createParser(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/simple.json"));
+        assertNotNull(parser);
+        assertSimple(parser);
+    }
+
+    @Test
+    public void nested() {
+        final JsonParser parser = Json.createParser(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/nested.json"));
+        assertNotNull(parser);
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("a", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_STRING, event);
+            assertEquals("b", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("c", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("d", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_ARRAY, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertEquals(1, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertEquals(2, parser.getInt());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_ARRAY, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_OBJECT, event);
+        }
+        {
+            assertFalse(parser.hasNext());
+        }
+        parser.close();
+    }
+
+    @Test
+    public void bigdecimal() {
+        final JsonParser parser = Json.createParser(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/bigdecimal.json"));
+        assertNotNull(parser);
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.START_OBJECT, event);
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.KEY_NAME, event);
+            assertEquals("a", parser.getString());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.VALUE_NUMBER, event);
+            assertFalse(parser.isIntegralNumber());
+            assertEquals(new BigDecimal("1.23E3"), parser.getBigDecimal());
+        }
+        {
+            assertTrue(parser.hasNext());
+            final JsonParser.Event event = parser.next();
+            assertNotNull(event);
+            assertEquals(JsonParser.Event.END_OBJECT, event);
+        }
+        {
+            assertFalse(parser.hasNext());
+        }
+        parser.close();
+    }
+
+    @Test
+    public void escaping() {
+        final JsonParser parser = Json.createParser(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/escaping.json"));
+        parser.next();
+        parser.next();
+        assertEquals("\"", parser.getString());
+        parser.next();
+        assertEquals("\t", parser.getString());
+        parser.next();
+        assertFalse(parser.hasNext());
+        parser.close();
+    }
+
+    @Test
+    public void dosProtected() {
+        // strings
+        {
+            final JsonParser parser = Json.createParserFactory(new HashMap<String, Object>() {{
+                        put(JsonParserFactoryImpl.MAX_STRING_LENGTH, 10);
+                    }}).createParser(new InputStream() {
+                private int index = 0;
+
+                @Override
+                public int read() throws IOException {
+                    switch (index) {
+                        case 0:
+                            index++;
+                            return '{';
+                        case 1:
+                            index++;
+                            return '"';
+                    }
+                    return 'a'; // infinite key
+                }
+            });
+            assertEquals(JsonParser.Event.START_OBJECT, parser.next());
+            try {
+                parser.next(); // should fail cause we try to make a OOME
+                fail();
+            } catch (final JsonParsingException expected) {
+                // no-op
+            }
+            parser.close();
+        }
+
+        // spaces
+        {
+            final JsonParser parser = Json.createParserFactory(new HashMap<String, Object>() {{
+                        put(JsonParserFactoryImpl.MAX_STRING_LENGTH, 10);
+                    }}).createParser(new InputStream() {
+                private int index = 0;
+
+                @Override
+                public int read() throws IOException {
+                    switch (index) {
+                        case 0:
+                            index++;
+                            return '{';
+                    }
+                    return ' '; // infinite spaces
+                }
+            });
+            assertEquals(JsonParser.Event.START_OBJECT, parser.next());
+            try { // should fail cause we try to make a OOME
+                while (parser.hasNext()) {
+                    parser.next();
+                }
+                fail();
+            } catch (final JsonParsingException expected) {
+                // no-op
+            }
+            parser.close();
+        }
+    }
+
+    @Test
+    public void hasNext() {
+        final JsonParser parser = new JsonStreamParser(new ByteArrayInputStream("{}".getBytes()), 1000);
+        assertTrue(parser.hasNext());
+        assertTrue(parser.hasNext());
+        assertTrue(parser.hasNext());
+        assertTrue(parser.hasNext());
+        assertEquals(JsonParser.Event.START_OBJECT, parser.next());
+        parser.close();
+    }
+
+    @Test(expected = JsonParsingException.class)
+    public void commaChecks() {
+        // using a reader as wrapper of parser
+        new JsonReaderImpl(new ByteArrayInputStream("{\"z\":\"b\"\"j\":\"d\"}".getBytes())).read();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.java
new file mode 100644
index 0000000..6520095
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonReaderImplTest.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.fleece.core;
+
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+public class JsonReaderImplTest {
+    @Test
+    public void simple() {
+        final JsonReader reader = Json.createReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("json/simple.json"));
+        assertNotNull(reader);
+        final JsonObject object = reader.readObject();
+        assertNotNull(object);
+        assertEquals(3, object.size());
+        assertEquals("b", object.getString("a"));
+        assertEquals(4, object.getInt("c"));
+        assertThat(object.get("d"), instanceOf(JsonArray.class));
+        final JsonArray array = object.getJsonArray("d");
+        assertNotNull(array);
+        assertEquals(2, array.size());
+        assertEquals(1, array.getInt(0));
+        assertEquals(2, array.getInt(1));
+        reader.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java b/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java
new file mode 100644
index 0000000..8c3d921
--- /dev/null
+++ b/fleece-core/src/test/java/org/apache/fleece/core/JsonWriterImplTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.fleece.core;
+
+import org.apache.fleece.core.JsonObjectImpl;
+import org.apache.fleece.core.JsonStringImpl;
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonWriter;
+import java.io.ByteArrayOutputStream;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonWriterImplTest {
+    @Test
+    public void writer() {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final JsonWriter writer = Json.createWriter(out);
+        final JsonObjectImpl value = new JsonObjectImpl();
+        value.putInternal("a", new JsonStringImpl("b"));
+        writer.write(value);
+        writer.close();
+        assertEquals("{\"a\":\"b\"}", new String(out.toByteArray()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/resources/json/array.json
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/resources/json/array.json b/fleece-core/src/test/resources/json/array.json
new file mode 100644
index 0000000..6e6bf04
--- /dev/null
+++ b/fleece-core/src/test/resources/json/array.json
@@ -0,0 +1,8 @@
+[
+    "a",
+    1,
+    {
+        "b": "c"
+    },
+    5
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/resources/json/bigdecimal.json
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/resources/json/bigdecimal.json b/fleece-core/src/test/resources/json/bigdecimal.json
new file mode 100644
index 0000000..7bbb4ea
--- /dev/null
+++ b/fleece-core/src/test/resources/json/bigdecimal.json
@@ -0,0 +1,3 @@
+{
+    "a": 1.23E3
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/resources/json/escaping.json
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/resources/json/escaping.json b/fleece-core/src/test/resources/json/escaping.json
new file mode 100644
index 0000000..87965a1
--- /dev/null
+++ b/fleece-core/src/test/resources/json/escaping.json
@@ -0,0 +1,4 @@
+[
+    "\"",
+    "\\t"
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/resources/json/nested.json
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/resources/json/nested.json b/fleece-core/src/test/resources/json/nested.json
new file mode 100644
index 0000000..d6f09f8
--- /dev/null
+++ b/fleece-core/src/test/resources/json/nested.json
@@ -0,0 +1,9 @@
+{
+    "a": "b",
+    "c": {
+        "d": [
+            1,
+            2
+        ]
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-core/src/test/resources/json/simple.json
----------------------------------------------------------------------
diff --git a/fleece-core/src/test/resources/json/simple.json b/fleece-core/src/test/resources/json/simple.json
new file mode 100644
index 0000000..07dced1
--- /dev/null
+++ b/fleece-core/src/test/resources/json/simple.json
@@ -0,0 +1,8 @@
+{
+    "a": "b",
+    "c": 4,
+    "d": [
+        1,
+        2
+    ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/pom.xml
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/pom.xml b/fleece-jaxrs/pom.xml
new file mode 100644
index 0000000..a211968
--- /dev/null
+++ b/fleece-jaxrs/pom.xml
@@ -0,0 +1,69 @@
+<?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/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>fleece</artifactId>
+    <groupId>org.apache.fleece</groupId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>fleece-jaxrs</artifactId>
+  <name>Fleece :: JAX-RS</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>javax.ws.rs</groupId>
+      <artifactId>javax.ws.rs-api</artifactId>
+      <version>2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.fleece</groupId>
+      <artifactId>fleece-mapper</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-client</artifactId>
+      <version>${cxf.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+      <version>${cxf.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-transports-local</artifactId>
+      <version>${cxf.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <properties>
+    <cxf.version>3.0.0</cxf.version>
+  </properties>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/DelegateProvider.java
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/DelegateProvider.java b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/DelegateProvider.java
new file mode 100644
index 0000000..12bcc80
--- /dev/null
+++ b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/DelegateProvider.java
@@ -0,0 +1,73 @@
+/*
+ * 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.fleece.jaxrs;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+public abstract class DelegateProvider<T> implements MessageBodyWriter<T>, MessageBodyReader<T>  {
+    private final MessageBodyReader<T> reader;
+    private final MessageBodyWriter<T> writer;
+
+    protected DelegateProvider(final MessageBodyReader<T> reader, final MessageBodyWriter<T> writer) {
+        this.reader = reader;
+        this.writer = writer;
+    }
+
+    @Override
+    public boolean isReadable(final Class<?> rawType, final Type genericType,
+                              final Annotation[] annotations, final MediaType mediaType) {
+        return reader.isReadable(rawType, genericType, annotations, mediaType);
+    }
+
+    @Override
+    public T readFrom(final Class<T> rawType, final Type genericType,
+                      final Annotation[] annotations, final MediaType mediaType,
+                      final MultivaluedMap<String, String> httpHeaders,
+                      final InputStream entityStream) throws IOException {
+        return reader.readFrom(rawType, genericType, annotations, mediaType, httpHeaders, entityStream);
+    }
+
+    @Override
+    public long getSize(final T t, final Class<?> rawType, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
+        return writer.getSize(t, rawType, genericType, annotations, mediaType);
+    }
+
+    @Override
+    public boolean isWriteable(final Class<?> rawType, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
+        return writer.isWriteable(rawType, genericType, annotations, mediaType);
+    }
+
+    @Override
+    public void writeTo(final T t, final Class<?> rawType, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders,
+                        final OutputStream entityStream) throws IOException {
+        writer.writeTo(t, rawType, genericType, annotations, mediaType, httpHeaders, entityStream);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyReader.java
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyReader.java b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyReader.java
new file mode 100644
index 0000000..4a06e25
--- /dev/null
+++ b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyReader.java
@@ -0,0 +1,75 @@
+/*
+ * 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.fleece.jaxrs;
+
+import org.apache.fleece.mapper.Mapper;
+import org.apache.fleece.mapper.MapperBuilder;
+
+import javax.json.JsonStructure;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import static javax.ws.rs.core.MediaType.WILDCARD;
+import static org.apache.fleece.jaxrs.Jsons.isJson;
+
+@Provider
+@Consumes(WILDCARD)
+public class FleeceMessageBodyReader<T> implements MessageBodyReader<T> {
+    private final Mapper mapper;
+
+    public FleeceMessageBodyReader() {
+        this(new MapperBuilder().setDoCloseOnStreams(false).build());
+    }
+
+    public FleeceMessageBodyReader(final Mapper mapper) {
+        this.mapper = mapper;
+    }
+
+    @Override
+    public boolean isReadable(final Class<?> rawType, final Type genericType,
+                              final Annotation[] annotations, final MediaType mediaType) {
+        return isJson(mediaType)
+                && InputStream.class != rawType && Reader.class != rawType
+                && String.class != rawType
+                && !JsonStructure.class.isAssignableFrom(rawType);
+    }
+
+    @Override
+    public T readFrom(final Class<T> rawType, final Type genericType,
+                      final Annotation[] annotations, final MediaType mediaType,
+                      final MultivaluedMap<String, String> httpHeaders,
+                      final InputStream entityStream) throws IOException {
+        if (rawType.isArray()) {
+            return (T) mapper.readArray(entityStream, rawType.getComponentType());
+        } else if (Collection.class.isAssignableFrom(rawType) && ParameterizedType.class.isInstance(genericType)) {
+            return (T) mapper.readCollection(entityStream, ParameterizedType.class.cast(genericType), rawType);
+        }
+        return mapper.readObject(entityStream, genericType);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyWriter.java
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyWriter.java b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyWriter.java
new file mode 100644
index 0000000..b893222
--- /dev/null
+++ b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceMessageBodyWriter.java
@@ -0,0 +1,88 @@
+/*
+ * 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.fleece.jaxrs;
+
+import org.apache.fleece.mapper.Mapper;
+import org.apache.fleece.mapper.MapperBuilder;
+
+import javax.json.JsonStructure;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import static javax.ws.rs.core.MediaType.WILDCARD;
+
+@Provider
+@Produces(WILDCARD)
+public class FleeceMessageBodyWriter<T> implements MessageBodyWriter<T> {
+    private final Mapper mapper;
+
+    public FleeceMessageBodyWriter() {
+        this(new MapperBuilder().setDoCloseOnStreams(false).build());
+    }
+
+    public FleeceMessageBodyWriter(final Mapper mapper) {
+        this.mapper = mapper;
+    }
+
+    @Override
+    public long getSize(final T t, final Class<?> rawType, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public boolean isWriteable(final Class<?> rawType, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
+        return Jsons.isJson(mediaType)
+                && InputStream.class != rawType
+                && OutputStream.class != rawType
+                && Writer.class != rawType
+                && StreamingOutput.class != rawType
+                && String.class != rawType
+                && Response.class != rawType
+                && !JsonStructure.class.isAssignableFrom(rawType);
+    }
+
+    @Override
+    public void writeTo(final T t, final Class<?> rawType, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders,
+                        final OutputStream entityStream) throws IOException {
+        if (rawType.isArray()) {
+            mapper.writeArray(t, entityStream);
+        } else if (Collection.class.isAssignableFrom(rawType) && ParameterizedType.class.isInstance(genericType)) {
+            mapper.writeArray(Collection.class.cast(t), entityStream);
+        } else {
+            mapper.writeObject(t, entityStream);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceProvider.java
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceProvider.java b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceProvider.java
new file mode 100644
index 0000000..6f1beba
--- /dev/null
+++ b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/FleeceProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fleece.jaxrs;
+
+import org.apache.fleece.mapper.Mapper;
+import org.apache.fleece.mapper.MapperBuilder;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.ext.Provider;
+
+import static javax.ws.rs.core.MediaType.WILDCARD;
+
+@Provider
+@Produces(WILDCARD)
+@Consumes(WILDCARD)
+public class FleeceProvider<T> extends DelegateProvider<T> {
+    public FleeceProvider(final Mapper mapper) {
+        super(new FleeceMessageBodyReader<T>(mapper), new FleeceMessageBodyWriter<T>(mapper));
+    }
+
+    public FleeceProvider() {
+        this(new MapperBuilder().setDoCloseOnStreams(false).build());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/Jsons.java
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/Jsons.java b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/Jsons.java
new file mode 100644
index 0000000..7872d28
--- /dev/null
+++ b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/Jsons.java
@@ -0,0 +1,39 @@
+/*
+ * 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.fleece.jaxrs;
+
+import javax.ws.rs.core.MediaType;
+
+public class Jsons {
+    public static boolean isJson(final MediaType mediaType) {
+        if (mediaType != null) {
+            final String subtype = mediaType.getSubtype();
+            return "json".equalsIgnoreCase(subtype)
+                    || "javascript".equals(subtype)
+                    || "x-json".equals(subtype)
+                    || "x-javascript".equals(subtype)
+                    || subtype.endsWith("+json");
+        }
+        return true;
+    }
+
+    private Jsons() {
+        // no-op
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fleece/blob/422e96f3/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/JsrMessageBodyReader.java
----------------------------------------------------------------------
diff --git a/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/JsrMessageBodyReader.java b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/JsrMessageBodyReader.java
new file mode 100644
index 0000000..f9a9e1c
--- /dev/null
+++ b/fleece-jaxrs/src/main/java/org/apache/fleece/jaxrs/JsrMessageBodyReader.java
@@ -0,0 +1,75 @@
+/*
+ * 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.fleece.jaxrs;
+
+import javax.json.Json;
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+import javax.json.JsonStructure;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+
+import static javax.ws.rs.core.MediaType.WILDCARD;
+
+@Provider
+@Consumes(WILDCARD)
+public class JsrMessageBodyReader implements MessageBodyReader<JsonStructure> {
+    private final JsonReaderFactory factory;
+    private final boolean closeStream;
+
+    public JsrMessageBodyReader() {
+        this(Json.createReaderFactory(Collections.<String, Object>emptyMap()), false);
+    }
+
+    public JsrMessageBodyReader(final JsonReaderFactory factory, final boolean closeStream) {
+        this.factory = factory;
+        this.closeStream = closeStream;
+    }
+
+    @Override
+    public boolean isReadable(final Class<?> aClass, final Type type,
+                              final Annotation[] annotations, final MediaType mediaType) {
+        return JsonStructure.class.isAssignableFrom(aClass);
+    }
+
+    @Override
+    public JsonStructure readFrom(final Class<JsonStructure> jsonStructureClass, final Type type,
+                                  final Annotation[] annotations, final MediaType mediaType,
+                                  final MultivaluedMap<String, String> stringStringMultivaluedMap,
+                                  final InputStream inputStream) throws IOException, WebApplicationException {
+        JsonReader reader = null;
+        try {
+            reader = factory.createReader(inputStream);
+            return reader.read();
+        } finally {
+            if (closeStream && reader != null) {
+                reader.close();
+            }
+        }
+    }
+}